@milpa/comal 0.1.6 → 0.2.0

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 (137) hide show
  1. package/README.md +19 -1
  2. package/backend/contracts/remote-commands-v1.json +12 -0
  3. package/backend/contracts/remote-workspace-v1.schema.json +287 -0
  4. package/governance/chrome-task-v10.json +323 -0
  5. package/governance/chrome-task-v11.json +355 -0
  6. package/governance/chrome-task-v12.json +387 -0
  7. package/governance/chrome-task-v13.json +419 -0
  8. package/governance/chrome-task-v3.json +99 -0
  9. package/governance/chrome-task-v4.json +131 -0
  10. package/governance/chrome-task-v5.json +163 -0
  11. package/governance/chrome-task-v6.json +195 -0
  12. package/governance/chrome-task-v7.json +227 -0
  13. package/governance/chrome-task-v8.json +259 -0
  14. package/governance/chrome-task-v9.json +291 -0
  15. package/governance/source-task-v1.json +1 -0
  16. package/governance/source-task-v2.json +1 -0
  17. package/package.json +15 -1
  18. package/src/agent-recovery-proof.mjs +12 -1
  19. package/src/agent.mjs +94 -30
  20. package/src/audit.mjs +21 -6
  21. package/src/auto-guard.mjs +48 -1
  22. package/src/chrome-candidate-recovery-proof.mjs +47 -0
  23. package/src/chrome-recovery-proof.mjs +6 -2
  24. package/src/chrome-source-syntax.mjs +93 -0
  25. package/src/cli.mjs +161 -56
  26. package/src/codex-provider.mjs +3 -2
  27. package/src/connection-settings.mjs +204 -0
  28. package/src/context-pressure.mjs +2 -1
  29. package/src/durable-agent-runs.mjs +5 -11
  30. package/src/event-channels.mjs +8 -0
  31. package/src/file-recovery-proof.mjs +11 -3
  32. package/src/file-recovery.mjs +24 -0
  33. package/src/gemini-provider.mjs +6 -1
  34. package/src/git-agent-revision.mjs +12 -0
  35. package/src/git-agent-workspaces.mjs +143 -0
  36. package/src/git-integration-lineage.mjs +20 -0
  37. package/src/git-integration-operations.mjs +200 -0
  38. package/src/git-integration-recovery-proof.mjs +70 -0
  39. package/src/git-page-cli-bridge.mjs +61 -0
  40. package/src/git-page-evidence.mjs +30 -0
  41. package/src/git-page-identity.mjs +10 -0
  42. package/src/git-page-proof.mjs +96 -0
  43. package/src/git-page-settlement.mjs +21 -0
  44. package/src/git-page-verification.mjs +134 -0
  45. package/src/git-plumbing.mjs +212 -0
  46. package/src/git-process-worker.mjs +50 -0
  47. package/src/git-source.mjs +110 -0
  48. package/src/git-workspaces.mjs +519 -0
  49. package/src/goals.mjs +13 -7
  50. package/src/house.mjs +246 -20
  51. package/src/i18n/cli-es.mjs +4 -0
  52. package/src/i18n/es.mjs +35 -0
  53. package/src/judges.mjs +27 -11
  54. package/src/mcp-runtime.mjs +15 -3
  55. package/src/model-controls.mjs +11 -1
  56. package/src/model-deadline.mjs +4 -3
  57. package/src/native-refusal.mjs +38 -0
  58. package/src/onboarding.mjs +88 -24
  59. package/src/openai-provider.mjs +14 -7
  60. package/src/operations.mjs +9 -2
  61. package/src/output-recovery.mjs +48 -0
  62. package/src/peer-tools.mjs +18 -8
  63. package/src/permission-controls.mjs +232 -0
  64. package/src/permission-rules.mjs +312 -0
  65. package/src/permissions.mjs +113 -30
  66. package/src/plugin-catalog.mjs +84 -0
  67. package/src/plugin-config.mjs +48 -6
  68. package/src/plugin-controls.mjs +332 -0
  69. package/src/plugin-notices.mjs +88 -0
  70. package/src/plugin-trust.mjs +104 -0
  71. package/src/plugins/chrome-lab/bridge.mjs +38 -12
  72. package/src/plugins/chrome-lab/client.mjs +103 -25
  73. package/src/plugins/chrome-lab/diagnostics.mjs +19 -0
  74. package/src/plugins/chrome-lab/index.mjs +116 -26
  75. package/src/plugins/chrome-lab/project.mjs +30 -2
  76. package/src/plugins/chrome-lab/scoped-input.mjs +135 -0
  77. package/src/plugins/chrome-lab/worker-diagnostics.mjs +51 -0
  78. package/src/plugins/open-relay/actions.mjs +205 -0
  79. package/src/plugins/open-relay/bound.mjs +47 -0
  80. package/src/plugins/open-relay/client.mjs +158 -0
  81. package/src/plugins/open-relay/dossier.mjs +119 -0
  82. package/src/plugins/open-relay/index.mjs +153 -0
  83. package/src/plugins/open-relay/lookup.mjs +45 -0
  84. package/src/plugins/open-relay/notices.mjs +79 -0
  85. package/src/plugins/open-relay/watch.mjs +127 -0
  86. package/src/plugins.mjs +72 -29
  87. package/src/private-fs.mjs +40 -0
  88. package/src/process-lock.mjs +24 -13
  89. package/src/provider-auth.mjs +14 -3
  90. package/src/provider.mjs +11 -2
  91. package/src/read-only-check.mjs +7 -3
  92. package/src/remote-control.mjs +29 -4
  93. package/src/remote-workspace.mjs +50 -0
  94. package/src/scope-ceiling.mjs +56 -0
  95. package/src/scoped-policy.mjs +39 -0
  96. package/src/secret-login.mjs +68 -0
  97. package/src/session-bus.mjs +41 -7
  98. package/src/session-projections-candidate.mjs +119 -0
  99. package/src/session-projections.mjs +119 -0
  100. package/src/session-read-cache.mjs +23 -3
  101. package/src/sessions.mjs +35 -11
  102. package/src/source-editor-role.mjs +13 -0
  103. package/src/source-ownership.mjs +73 -0
  104. package/src/source-proposal-proof.mjs +55 -0
  105. package/src/source-proposals.mjs +118 -0
  106. package/src/source-requests.mjs +148 -0
  107. package/src/source-task-proof.mjs +24 -0
  108. package/src/source-task-revision-proof.mjs +44 -0
  109. package/src/subagents.mjs +187 -87
  110. package/src/tool-packs.mjs +5 -5
  111. package/src/tools.mjs +82 -15
  112. package/src/trusted-context.mjs +5 -1
  113. package/src/tui/connection-transition.mjs +1 -1
  114. package/src/tui/controller.mjs +8 -4
  115. package/src/tui/discovery.mjs +24 -12
  116. package/src/tui/inbound-blocks.mjs +55 -0
  117. package/src/tui/present.mjs +504 -0
  118. package/src/tui/screen.mjs +49 -13
  119. package/src/windows-private.ps1 +61 -0
  120. package/src/work-assertions.mjs +6 -2
  121. package/src/work-plan.mjs +34 -8
  122. package/src/work-state.mjs +15 -9
  123. package/src/workflows/chrome-page-cases.mjs +156 -0
  124. package/src/workflows/chrome-page-checkpoint-reader.mjs +10 -0
  125. package/src/workflows/chrome-page-diagnostics.mjs +113 -0
  126. package/src/workflows/chrome-page-law.mjs +48 -0
  127. package/src/workflows/chrome-page-recovery-host-v13.mjs +14 -0
  128. package/src/workflows/chrome-page-recovery-v13.mjs +104 -0
  129. package/src/workflows/chrome-page-task.mjs +429 -0
  130. package/src/workflows/chrome-page-verifier.mjs +123 -0
  131. package/src/workflows/chrome-source.mjs +73 -0
  132. package/src/workflows/chrome-supplied-inputs.mjs +43 -0
  133. package/src/workflows/chrome-task-adoption.mjs +122 -0
  134. package/src/workflows/chrome-task.mjs +379 -74
  135. package/src/workflows/source-task-v1.mjs +124 -0
  136. package/src/workflows/source-task-v2.mjs +227 -0
  137. package/src/workflows/source-task.mjs +13 -0
@@ -0,0 +1,124 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {randomUUID} from 'node:crypto';
4
+ import {digest,immutable} from '../operations.mjs';
5
+ import Ajv from 'ajv';
6
+ import {compileGovernance} from '../governance.mjs';
7
+ import {workProjection,registerWorkOperations} from '../work-plan.mjs';
8
+ import {auditExecution} from '../audit.mjs';
9
+ import {GitWorkspaces} from '../git-workspaces.mjs';
10
+ import {inspectGitSource} from '../git-source.mjs';
11
+ import {gitIntegrationDefinitions} from '../git-integration-operations.mjs';
12
+ import {acquireProcessLock} from '../process-lock.mjs';
13
+ import {SOURCE_TASK_MECHANISM,savedSourceTask,sourceTaskFactValid} from '../source-task-proof.mjs';
14
+ const source='source_task_host',empty={type:'object',properties:{},additionalProperties:false},fail=(message,code='SOURCE_TASK_CONFLICT',details)=>Object.assign(new Error(message),{code,...(details?{details}:{})});
15
+ const str=n=>({type:'string',minLength:1,maxLength:n});
16
+ export const sourceTaskSchema={type:'object',additionalProperties:false,required:['criterion','editors','verification','target'],properties:{criterion:str(4000),target:gitIntegrationDefinitions[0].input_schema.properties.target,source:{type:'string',enum:['head','working']},editors:{type:'array',minItems:1,maxItems:3,items:{type:'object',additionalProperties:false,required:['id','prompt'],properties:{id:{type:'string',pattern:'^[a-zA-Z0-9][a-zA-Z0-9_-]{0,39}$'},prompt:str(12000),max_turns:{type:'integer',minimum:1,maximum:12}}}},verification:gitIntegrationDefinitions[0].input_schema.properties.verification}};
17
+ const validateSourceTask=new Ajv({strict:false,allErrors:true}).compile(sourceTaskSchema);
18
+ export function normalizeSourceTask(task){if(!validateSourceTask(task))throw fail('Invalid source contract: '+validateSourceTask.errors.map(e=>e.instancePath+' '+e.message).join('; '));if(Buffer.byteLength(JSON.stringify(task))>32768||new Set(task.editors.map(e=>e.id)).size!==task.editors.length)throw fail('Source task must be at most 32 KiB with unique editor IDs.');return immutable({...structuredClone(task),source:task.source??'working'});}
19
+ export async function readSourceTask(file){const bytes=await readFile(file);if(bytes.length>32768)throw fail('Source task exceeds 32 KiB.');return normalizeSourceTask(JSON.parse(bytes));}
20
+ export async function prepareSourceTaskV1({cwd,store,house,executor,policy,subagents,integrationOperations,workspace,task,sessionId,actor={id:'root-agent',kind:'agent'},fault=async()=>{}}){
21
+ let book=await store.load(sessionId),accepted=savedSourceTask(book);if(accepted&&accepted.mechanism!==SOURCE_TASK_MECHANISM)throw fail('This frozen coordinator only restores source-task v1.');if(!task&&!accepted)return null;if(!book.journalAudit.verified||auditExecution(book,house.stateContext()).violations.length)throw fail('A verified source-task journal is required.','AUDIT_INVALID');
22
+ const brief=normalizeSourceTask(task??accepted.task),taskDigest=digest(brief),goal=book.goal?.id??null,epoch=book.rows.findLastIndex(r=>r.type==='reset');
23
+ if(accepted&&(!sourceTaskFactValid(accepted)||accepted.task_digest!==taskDigest||digest(accepted.actor)!==digest(actor)))throw fail('The original source contract/owner changed. Start a new session for another accepted criterion.');
24
+ if(!accepted&&workProjection(book))throw fail('This session already has accepted work. A source contract cannot replace it.');
25
+ for(const name of ['AgentBatch','AgentContinue','AgentResume',...gitIntegrationDefinitions.map(t=>t.name)])if(!house.registry.get(name))throw fail('Missing native source coordination operation: '+name);
26
+ registerWorkOperations({store,house});
27
+ const plan=compileGovernance(JSON.parse(await readFile(new URL('../../governance/source-task-v1.json',import.meta.url))),['runtime:source-task.scope','runtime:source-task.close']);
28
+ if(!integrationOperations||!workspace?.registerSettlement)throw fail('The source task needs native integration and settlement ports.');
29
+ const ownNames=new Set(['SourceDeliver','SourceStatus','SourceVerify','SourceEvidence']),publicNames=new Set(['SourceDeliver','SourceStatus','SourceEvidence','EvidenceRead']);
30
+ const contract={id:'source-task',title:'Implement and verify accepted source contract',criterion:brief.criterion,capabilities:['workspace.git'],checks:[{step_id:'combined-source',operation:'SourceVerify',arguments:{},assertions:[{kind:'json_equals',pointer:'/status',value:'verified'}]}]};
31
+ const state=async({historical=false}={})=>{const s=await store.load(sessionId),a=savedSourceTask(s);if(!s.journalAudit.verified||auditExecution(s,house.stateContext()).violations.length||!sourceTaskFactValid(a)||a.task_digest!==taskDigest||a.binding.reset_ref!==s.rows.findLastIndex(r=>r.type==='reset'))throw fail('The accepted source task/reset scope changed.','SOURCE_TASK_SCOPE_CHANGED');const execution=s.rows.find(r=>r.type==='source_task_execution_bound'&&r.source===source&&r.task_digest===taskDigest);if(!historical&&execution&&execution.goal_owner!==(s.goal?.status==='active'?s.goal.id:null))throw fail('The original active source execution goal changed.','SOURCE_TASK_SCOPE_CHANGED');const work=workProjection(s,house.stateContext());if(work){const defs=s.rows[work.plan.ref]?.tasks,definition=defs?.[0]&&Object.fromEntries(Object.entries(defs[0]).filter(([k])=>!['definition_digest','work_digest'].includes(k)));if(work.plan.version!==1||defs.length!==1||digest(definition)!==digest({...contract,depends_on:[]}))throw fail('The accepted source work/check definition changed.');}return s;};
32
+ const inputForEditors={tasks:brief.editors.map(e=>({prompt:e.prompt,description:e.id,isolation:'git',git_source:brief.source,work_tracking:'parent',...(e.max_turns?{max_turns:e.max_turns}:{})}))};
33
+ const stages=s=>s.rows.filter(r=>r.type==='source_task_stage'&&r.source===source&&r.task_digest===taskDigest&&r.session_id===sessionId);
34
+ const preparedStage=(s,key)=>stages(s).find(r=>r.key===key);
35
+ const proposal=(s,stage)=>s.rows.find(r=>r.type==='house_proposal'&&r.call_id===stage.call_id&&r.operation===stage.operation&&r.arguments_digest===stage.arguments_digest&&digest(r.actor)===digest(actor));
36
+ const stageReceipt=(s,stage)=>{const p=proposal(s,stage);return p&&s.rows.findLast(r=>r.type==='house_receipt'&&r.invocation_id===p.invocation_id);};
37
+ const evidenceCall=call=>{const {integration_id,...input}=call.input;return{name:'SourceEvidence',input};};
38
+ const projectEvidence=items=>items?.map(item=>({...item,read_call:evidenceCall(item.read_call)}))??[];
39
+ const parse=r=>{if(r?.status!=='returned'||r.output_truncated||r.output_digest!==digest(r.output))throw fail('The original native stage did not return a complete durable outcome.','SOURCE_TASK_STAGE_CLOSED',{receipt_id:r?.invocation_id,code:r?.code??null});return JSON.parse(r.output);};
40
+ const recordCall=async(name,input,o)=>o.recordWork({type:'source_task_call_prepared',source,operation:name,arguments_digest:digest(input),task_digest:taskDigest});
41
+ const frame=async(o,options)=>{if(o.sessionId!==sessionId||digest(o.actor)!==digest(actor))throw fail('The accepted source task belongs to another actor/session.','SOURCE_TASK_OWNER');await state(options);await o.revalidate();};
42
+ const stage=async(key,name,input,o)=>{
43
+ let s=await state(),saved=preparedStage(s,key);if(saved&&(saved.operation!==name||saved.arguments_digest!==digest(input)||digest(saved.input)!==digest(input)))throw fail('A source stage cannot replace its original native intent.');
44
+ if(!saved){await o.recordWork({type:'source_task_stage',source,task_digest:taskDigest,key,operation:name,input,arguments_digest:digest(input),call_id:randomUUID()});s=await state();saved=preparedStage(s,key);}
45
+ const p=proposal(s,saved);if(p){const r=stageReceipt(s,saved);if(!r)throw fail('A source stage has an unsettled native dispatch. Recover the same session; no effect was replayed.','SOURCE_TASK_UNCERTAIN');return{value:r.status==='returned'?parse(r):null,receipt:r,proposal:p,stage:saved};}
46
+ await frame(o);if(key==='editors'&&(await inspectGitSource({cwd,signal:o.signal})).source_identity!==savedSourceTask(await state()).binding.source_identity)throw fail('The accepted parent source changed before editor launch.','SOURCE_TASK_STALE');await o.recordWork({type:'source_task_dispatch',source,task_digest:taskDigest,stage_ref:s.rows.indexOf(saved),stage_digest:saved.audit.sha256,key});
47
+ await o.invoke(name,input,{signal:o.signal,callId:saved.call_id,beforeEffect:()=>frame(o)});s=await state();const r=stageReceipt(s,saved);return{value:parse(r),receipt:r,proposal:proposal(s,saved),stage:saved};
48
+ };
49
+ // Retry only a lost handler reply, preserving the native intent/attempt. The
50
+ // kernel remains responsible for refusing an uncertain mutation or check.
51
+ const recoverStage=async(key,name,input,o)=>{
52
+ for(let n=0;;n++){
53
+ const value=await stage(n?key+'-recovered-'+n:key,name,input,o);
54
+ if(value.value)return value;
55
+ if(!value.receipt.recovery&&!['HOUSE_RECOVERY_NOT_STARTED','HOUSE_RECOVERY_NOT_DISPATCHED'].includes(value.receipt.code))throw fail('The original native stage is closed; no fresh intent was substituted.','SOURCE_TASK_STAGE_CLOSED',{receipt_id:value.receipt.invocation_id});
56
+ }
57
+ };
58
+ const integration=async(options)=>{const s=await state(options),saved=preparedStage(s,'prepare');if(!saved)return null;const r=stageReceipt(s,saved);if(r?.status==='returned')return parse(r).integration_id;const intent=s.rows.find(row=>row.type==='git_integration_intent'&&row.operation==='GitIntegrationPrepare'&&row.input.request_id==='source-'+taskDigest.slice(0,40)&&row.owner_digest===digest({actor,session_id:sessionId}));return intent?.binding.resolved.integration_id??null;};
59
+ const inspect=async(o,{invoke=true,historical=false}={})=>{
60
+ const id=await integration({historical});if(!id)return{status:'not_prepared',task_digest:taskDigest,asked:'unjudged'};
61
+ if(!invoke)throw fail('A native observation context is required.');
62
+ const result=await integrationOperations.status({integration_id:id},o),status=result.integrations[0];
63
+ const kernel=new GitWorkspaces({cwd}),resource=await kernel.status(id,{owner:{actor,session_id:sessionId}}),prepared=resource.completed.find(r=>r.action==='integration_prepare')?.output;
64
+ let target=null,current=null;if(prepared){target=await kernel.rootRef('refs/comal/integrations/'+brief.target,o.signal);current=await inspectGitSource({cwd:prepared.worktree,signal:o.signal});}
65
+ const rootSource=await inspectGitSource({cwd,signal:o.signal});
66
+ const valid=rootSource.source_identity===savedSourceTask(await state({historical})).binding.source_identity&&resource.lifecycle==='retained'&&!resource.pending.length&&status.verification==='passed_declared_checks'&&status.applied===status.candidate&&target===status.candidate&&current?.head===status.candidate&&!current.dirty&&current.source_identity===prepared.source_identity;
67
+ o.observe?.({kind:'source_task_ref_observation',task_digest:taskDigest,integration_id:id,target_ref:'refs/comal/integrations/'+brief.target,observed_commit:target,candidate:status.candidate,source_identity:current?.source_identity??null});
68
+ return{status:valid?'verified':'needs_verification',task_digest:taskDigest,integration_id:id,candidate:status.candidate,target_ref:'refs/comal/integrations/'+brief.target,observed_commit:target,verification:status.verification,pending:status.pending,rejected:status.rejected,evidence:projectEvidence(status.evidence),scope:'native_source_and_declared_checks_not_human_acceptance',asked:'unjudged'};
69
+ };
70
+ const locks=new Map();
71
+ const lock=async o=>{const lease=await acquireProcessLock(path.join(store.dir,'.source-task-'+digest({sessionId,actor,taskDigest})+'.lock'),{resource:{session_id:sessionId,invocation_id:o.invocation_id},validate:async old=>{const s=await state();if(old.resource?.session_id!==sessionId||!s.rows.some(r=>r.type==='house_receipt'&&r.invocation_id===old.resource.invocation_id))throw fail('Recover the original coordinator before continuing.','SOURCE_TASK_UNCERTAIN');}}).catch(error=>{if(error.code==='SESSION_IN_USE')throw fail('The accepted source pipeline is already running.','SOURCE_TASK_BUSY');throw error;});locks.set(o.invocation_id,lease);};
72
+ const settle=async({invocation_id,receiptRecorded})=>{const lease=locks.get(invocation_id);if(!lease)return;locks.delete(invocation_id);if(receiptRecorded)await lease.release();else await lease.file.close();};
73
+ const deliver=async(input,o)=>{
74
+ if(policy.mode==='plan')throw fail('Plan mode cannot start the source delivery pipeline.','HOUSE_REFUSED');
75
+ await lock(o);await frame(o);const entered=await state();if(!entered.rows.some(r=>r.type==='source_task_execution_bound'&&r.source===source&&r.task_digest===taskDigest))await o.recordWork({type:'source_task_execution_bound',source,task_digest:taskDigest,goal_owner:entered.goal?.status==='active'?entered.goal.id:null});if(workProjection(await state(),house.stateContext()).counts.done===1){const observed=JSON.parse(await o.invoke('SourceVerify',{}, {signal:o.signal}));if(observed.status!=='verified')throw fail('The recorded delivery is historical; its source or current target changed.','SOURCE_TASK_STALE');return{...observed,status:'done',reused:true};}
76
+ await fault('before_editors',{o});let s=await state(),launch=preparedStage(s,'editors'),batch;
77
+ if(!launch||!proposal(s,launch))batch=await stage('editors','AgentBatch',inputForEditors,o);
78
+ else{const p=proposal(s,launch),r=stageReceipt(s,launch);if(!r)throw fail('The original editor launch is unsettled; recover it before continuing.','SOURCE_TASK_UNCERTAIN');if(r.status==='returned')batch={value:parse(r),receipt:r,proposal:p};else if(['HOUSE_RECOVERY_CONTINUE','HOUSE_RECOVERY_NOT_DISPATCHED'].includes(r.code))batch=await recoverStage('editors-continue','AgentContinue',{invocation_id:p.invocation_id},o);else throw fail('The original editor batch was refused/failed; it was not restarted.','SOURCE_TASK_EDITORS_CLOSED');}
79
+ await subagents.reconcile(sessionId);s=await state();launch=preparedStage(s,'editors');const parent=proposal(s,launch),runs=(await subagents.runs(sessionId)).filter(run=>run.parent_invocation_id===parent.invocation_id);
80
+ for(const run of runs)if(['paused','interrupted'].includes(run.status)&&input.resume_editors===true){const resumed=await recoverStage('resume-'+run.run_id+'-'+run.turn_id,'AgentResume',{run_id:run.run_id},o);if(!resumed.value)throw fail('The original editor resume is closed; inspect its native outcome.','SOURCE_TASK_STAGE_CLOSED');}
81
+ const current=(await subagents.runs(sessionId)).filter(run=>run.parent_invocation_id===parent.invocation_id);if(current.length!==brief.editors.length||current.some(run=>run.status!=='completed'||!run.source_offer))return{status:'needs_editor_attention',task_digest:taskDigest,editors:current.map(r=>({run_id:r.run_id,status:r.status,turn_state:r.turn_state,error:r.error,result_read_call:r.result_read_call})),next_action:'Inspect evidence; SourceDeliver resume_editors:true explicitly resumes only paused/interrupted original editors. No new batch is created.',asked:'unjudged'};
82
+ await fault('editors_returned',{o,runs:current});
83
+ const prepInput={request_id:'source-'+taskDigest.slice(0,40),target:brief.target,run_ids:current.map(r=>r.run_id),verification:brief.verification};const prep=await recoverStage('prepare','GitIntegrationPrepare',prepInput,o);
84
+ const id=prep.value.integration_id;await fault('prepared',{o,integration_id:id});let status=JSON.parse(await o.invoke('GitIntegrationStatus',{integration_id:id},{signal:o.signal})).integrations[0];
85
+ if(status.verification!=='passed_declared_checks'){
86
+ const failed=status.rejected.filter(r=>r.action==='integration_verify').length;
87
+ if(failed&&!input.retry_checks)return{status:'needs_correction',task_digest:taskDigest,integration_id:id,evidence:projectEvidence(status.evidence),next_action:'Accepted combined checks failed. Criteria remain immutable. Inspect/correct source before a deliberate retry; no check was repeated.',asked:'unjudged'};
88
+ const number=failed?String(failed):'first',attempt_id='source-check-'+number;await recoverStage('verify-'+number,'GitIntegrationVerify',{integration_id:id,attempt_id},o);
89
+ }
90
+ await fault('verified',{o,integration_id:id});status=JSON.parse(await o.invoke('GitIntegrationStatus',{integration_id:id},{signal:o.signal})).integrations[0];if(status.verification!=='passed_declared_checks')throw fail('No passed native combined verification exists.','SOURCE_TASK_CHECK_FAILED');
91
+ if(!status.applied)await recoverStage('apply','GitIntegrationApply',{integration_id:id,attempt_id:'source-apply'},o);
92
+ await fault('applied',{o,integration_id:id});const observed=await inspect(o);if(observed.status!=='verified')throw fail('Current source/ref differs from the passed native candidate.','SOURCE_TASK_STALE');
93
+ const work=workProjection(await state(),house.stateContext());if(work.counts.done!==1){const check=await o.invoke('WorkStepRun',{task_id:'source-task',plan_version:1,step_id:'combined-source',recheck:true},{signal:o.signal});await fault('close_check_returned',{o,check});await o.invoke('WorkTaskUpdate',{task_id:'source-task',plan_version:1,status:'done'},{signal:o.signal});}
94
+ await fault('closed',{o});return{...observed,status:'done',editors:current.map(r=>({run_id:r.run_id,source_offer:r.source_offer})),acceptance:'unjudged'};
95
+ };
96
+ const publicWork=book=>{const w=workProjection(book,house.stateContext());return{counts:w.counts,cards:w.cards.map(({title,status})=>({title,status})),current:w.current?{title:w.current.title,status:w.current.status,criterion:w.current.criterion}:null,close_call:{name:'SourceDeliver',input:{}},administrative_controls:'host_owned',permissions_from_plan:false};};
97
+ const evidenceSchema=structuredClone(gitIntegrationDefinitions.find(d=>d.name==='GitIntegrationEvidence').input_schema);delete evidenceSchema.properties.integration_id;evidenceSchema.required=evidenceSchema.required.filter(k=>k!=='integration_id');
98
+ const defs=[{name:'SourceDeliver',description:'Continue the SAME accepted source task. The house owns editor/check/ref/work identities. Native AgentBatch editors implement source, then native combined checks and reserved-ref CAS establish closure. resume_editors explicitly resumes paused/interrupted originals; retry_checks explicitly retries known failed checks. Never rewrites criteria or restarts an uncertain batch.',input_schema:{type:'object',properties:{resume_editors:{type:'boolean'},retry_checks:{type:'boolean'}},additionalProperties:false},run:deliver},{name:'SourceEvidence',description:'Page exact native combined-check evidence for THIS accepted task, including failed/uncertain outcomes. Use the read_call supplied by SourceStatus. No owner, integration ID, permission or success override.',input_schema:evidenceSchema,run:async(input,o)=>{const id=await integration({historical:true});if(!id)throw fail('No native combined verifier exists yet.','SOURCE_TASK_NOT_PREPARED');const result=await integrationOperations.evidence({...input,integration_id:id},o);if(result.next_call)result.next_call=evidenceCall(result.next_call);return result;}},...['SourceStatus','SourceVerify'].map(name=>({name,description:name==='SourceStatus'?'Read accepted source-task state, original editor outcomes and native evidence; no polling or permission grant.':'Host-only declared check: observe passed native combined checks, current candidate source and actual reserved target. No model success flag.',input_schema:empty,run:async(_,o)=>name==='SourceVerify'?inspect(o):{task:brief,task_digest:taskDigest,work:publicWork(await state({historical:true})),editors:(await subagents.runs(sessionId)).map(r=>({run_id:r.run_id,status:r.status,source_offer:r.source_offer,error:r.error})),integration:await inspect(o,{historical:true})}}))];
99
+ house.registry.replace([...ownNames],defs.map(d=>({...d,revision:SOURCE_TASK_MECHANISM,steps:d.name==='SourceDeliver',permission:d.name==='SourceDeliver'?d.name:d.name==='SourceEvidence'?'GitIntegrationEvidence':'GitIntegrationStatus',effects:d.name==='SourceDeliver'?{workspace:'unknown',external:'unknown',process:'unknown',authority:'none'}:{workspace:'read',external:'none',process:'read',authority:'none'},run:async(input,o)=>{await recordCall(d.name,input,o);await frame(o,{historical:['SourceStatus','SourceEvidence'].includes(d.name)});return JSON.stringify(await d.run(input,o));}})));
100
+ if(accepted)await workspace.recoverInterrupted();
101
+ let setupTurn;
102
+ if(house.registry.get('ToolPack')){setupTurn=randomUUID();await store.append(sessionId,{type:'tool_turn',source:'capability_host',actor,id:setupTurn,open:true});await executor.execute('ToolPack',{action:'acquire',pack:'workspace.git'});}
103
+ try{
104
+ if(!accepted){const observed=JSON.parse(await executor.execute('GitInspect',{}));const binding={cwd,session_id:sessionId,actor,goal_id:goal,reset_ref:epoch,source_identity:observed.source_identity};accepted=await store.append(sessionId,{type:'source_task_accepted',source,mechanism:SOURCE_TASK_MECHANISM,house:cwd,session_id:sessionId,actor,surface:'agent',task:brief,task_digest:taskDigest,governance_plan_hash:plan.plan_hash,binding,binding_digest:digest(binding)});}
105
+ if(!workProjection(await store.load(sessionId)))await executor.execute('WorkPlanSet',{objective:brief.criterion,expected_version:0,tasks:[contract]});
106
+ if(workProjection(await store.load(sessionId)).current?.status==='pending')await executor.execute('WorkTaskUpdate',{task_id:'source-task',plan_version:1,status:'in_progress'});
107
+ if(house.registry.get('ToolPack'))await executor.execute('ToolPack',{action:'acquire',pack:'workspace.git'});
108
+ }finally{if(setupTurn)await store.append(sessionId,{type:'tool_turn',source:'capability_host',actor,id:setupTurn,open:false});}
109
+ const scope=async v=>{if(v.actor.kind==='human')return true;try{await state({historical:['SourceStatus','SourceEvidence'].includes(v.operation)&&v.session_id===sessionId});}catch(error){return{allowed:false,reason:error.message};}if(v.session_id!==sessionId)return true;if(digest(v.actor)!==digest(actor))return false;if(ownNames.has(v.operation))return v.operation!=='SourceVerify'||['WorkStepRun','SourceDeliver'].includes(v.parent_operation);if(v.operation==='EvidenceRead')return true;if(v.operation==='AgentBatch')return v.parent_operation==='SourceDeliver'&&digest(v.input)===digest(inputForEditors);if(['AgentContinue','AgentResume','GitIntegrationPrepare','GitIntegrationVerify','GitIntegrationApply'].includes(v.operation))return v.parent_operation==='SourceDeliver';if(v.operation==='GitIntegrationStatus')return ['SourceDeliver','SourceVerify','SourceStatus'].includes(v.parent_operation);if(v.operation==='WorkStepRun')return v.parent_operation==='SourceDeliver'&&digest(v.input)===digest({task_id:'source-task',plan_version:1,step_id:'combined-source',recheck:true});if(v.operation==='WorkTaskUpdate')return v.parent_operation==='SourceDeliver'&&digest(v.input)===digest({task_id:'source-task',plan_version:1,status:'done'});return{allowed:false,reason:'This accepted source task uses native source actions; generic work/shell administration is unavailable.'};};
110
+ const close=async v=>{if(v.session_id!==sessionId||v.actor.kind==='human'||v.input.status!=='done')return true;const s=await state(),r=s.rows.findLast(row=>row.type==='house_receipt'&&row.operation==='SourceVerify'&&row.status==='returned');if(!r||JSON.parse(r.output).status!=='verified')return false;const value=JSON.parse(r.output),kernel=new GitWorkspaces({cwd});return await kernel.rootRef(value.target_ref)===value.candidate;};
111
+ if(accepted.governance_plan_hash&&accepted.governance_plan_hash!==plan.plan_hash)throw fail('The saved source workflow policy changed.');
112
+ await house.activateGovernance({sessionId,plan,bindings:{'runtime:source-task.scope':{revision:SOURCE_TASK_MECHANISM,operations:['*'],configuration_digest:taskDigest,check:scope},'runtime:source-task.close':{revision:SOURCE_TASK_MECHANISM,operations:['WorkTaskUpdate'],configuration_digest:taskDigest,check:close}}});
113
+ const retireSettlement=workspace.registerSettlement(settle);
114
+ const project=snapshot=>{const definitions=snapshot.definitions().filter(d=>publicNames.has(d.name)),offered=new Set(definitions.map(d=>d.name));return{...snapshot,definitions:()=>definitions,execute:(name,input,options={})=>{if(options.modelProposed&&!offered.has(name))throw fail('The house owns accepted source bookkeeping; use the offered source actions.','SOURCE_TASK_SCOPE');return snapshot.execute(name,input,options);}};};
115
+ return{task:brief,taskDigest,system:'Accepted source task: '+JSON.stringify(brief)+'. Use SourceDeliver to implement through the original native editors, combine, verify and close. SourceStatus/SourceEvidence/EvidenceRead inspect missing failure evidence. The house derives IDs and performs declared verification. Never create generic work plans, administer Git, change criteria or infer acceptance. Report limits on refusal/uncertainty.',dispose:()=>{retireSettlement();house.retireGovernance(sessionId);house.registry.replace([...ownNames],[]);},wrap:tools=>({...tools,house,policy,workflow:'source-task',definitions:()=>project(tools.snapshot()).definitions(),snapshot:()=>project(tools.snapshot()),execute:(n,i,o)=>project(tools.snapshot()).execute(n,i,o)}),settle,
116
+ async finishResult(final,context){
117
+ let delivery={status:'open',task_digest:taskDigest,asked:'unjudged',scope:'accepted_source_and_declared_checks_not_human_acceptance'};
118
+ const s=await state({historical:true}),closed=s.rows.slice(context.baseline).findLast(r=>r.type==='house_receipt'&&r.operation==='SourceDeliver'&&r.status==='returned'&&JSON.parse(r.output).status==='done');
119
+ if(!final.is_error&&!final.work_stop_reason&&closed){try{await state();const observed=JSON.parse(await executor.execute('SourceStatus',{}, {signal:context.signal}));if(observed.integration.status!=='verified')throw fail('Final native source/ref observation failed.','SOURCE_TASK_STALE');const read=(await state()).rows.findLast(r=>r.type==='house_receipt'&&r.operation==='SourceStatus'&&r.status==='returned');delivery={...delivery,status:'verified',receipt_id:read.invocation_id,candidate:observed.integration.candidate,target_ref:observed.integration.target_ref};}catch(error){delivery={...delivery,status:'needs_verification',code:error.code??'SOURCE_TASK_FINAL_CHECK'};final.is_error=true;final.work_stop_reason='verification_failed';final.result+='\nThe final source/ref check failed; delivery remains unverified.';}}
120
+ else if(!final.is_error){final.is_error=true;final.work_stop_reason??='source_task_open';final.result+='\nThe accepted source delivery remains unverified.';}
121
+ final.delivery=delivery;await store.append(sessionId,{type:'source_task_delivery',source,...delivery});
122
+ },
123
+ async completedResult(calls=[]){const s=await state(),r=s.rows.findLast(r=>r.type==='house_receipt'&&r.operation==='SourceDeliver'&&r.status==='returned'&&calls.some(c=>c.id===r.call_id&&c.name==='SourceDeliver'));if(!r||workProjection(s,house.stateContext()).counts.done!==1)return null;const value=JSON.parse(r.output);if(value.status!=='done')return null;return{terminal:true,receipt_id:r.invocation_id,result:`Source candidate verified at ${value.target_ref}: ${value.candidate}. Accepted combined checks passed; evidence ${r.invocation_id}. Human acceptance remains unjudged.`};}};
124
+ }
@@ -0,0 +1,227 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {randomUUID} from 'node:crypto';
4
+ import {digest,immutable} from '../operations.mjs';
5
+ import {compileGovernance} from '../governance.mjs';
6
+ import {inspectWork,workProjection,registerWorkOperations} from '../work-plan.mjs';
7
+ import {auditExecution} from '../audit.mjs';
8
+ import {GitWorkspaces} from '../git-workspaces.mjs';
9
+ import {inspectGitSource} from '../git-source.mjs';
10
+ import {gitIntegrationDefinitions} from '../git-integration-operations.mjs';
11
+ import {acquireProcessLock} from '../process-lock.mjs';
12
+ import {SOURCE_TASK_MECHANISM_V2 as SOURCE_TASK_MECHANISM,savedSourceTask,sourceTaskFactValid} from '../source-task-proof.mjs';
13
+ const source='source_task_host',empty={type:'object',properties:{},additionalProperties:false},fail=(message,code='SOURCE_TASK_CONFLICT',details)=>Object.assign(new Error(message),{code,...(details?{details}:{})});
14
+ import {normalizeSourceTask} from './source-task-v1.mjs';
15
+ import {sourceTaskRevisions,correctionRequest,correctionEditor} from '../source-task-revision-proof.mjs';
16
+ export async function prepareSourceTaskV2({cwd,store,house,executor,policy,subagents,integrationOperations,workspace,task,sessionId,actor={id:'root-agent',kind:'agent'},fault=async()=>{}}){
17
+ let book=await store.load(sessionId),accepted=savedSourceTask(book);if(accepted&&accepted.mechanism!==SOURCE_TASK_MECHANISM)throw fail('This coordinator only restores source-task v2.');if(!task&&!accepted)return null;if(!book.journalAudit.verified||auditExecution(book,house.stateContext()).violations.length)throw fail('A verified source-task journal is required.','AUDIT_INVALID');
18
+ const brief=normalizeSourceTask(task??accepted.task),taskDigest=digest(brief),goal=book.goal?.id??null,epoch=book.rows.findLastIndex(r=>r.type==='reset');
19
+ if(accepted&&(!sourceTaskFactValid(accepted)||accepted.task_digest!==taskDigest||digest(accepted.actor)!==digest(actor)))throw fail('The original source contract/owner changed. Start a new session for another accepted criterion.');
20
+ if(!accepted&&workProjection(book))throw fail('This session already has accepted work. A source contract cannot replace it.');
21
+ for(const name of ['Agent','AgentBatch','AgentContinue','AgentResume',...gitIntegrationDefinitions.map(t=>t.name)])if(!house.registry.get(name))throw fail('Missing native source coordination operation: '+name);
22
+ registerWorkOperations({store,house});
23
+ const plan=compileGovernance(JSON.parse(await readFile(new URL('../../governance/source-task-v2.json',import.meta.url))),['runtime:source-task.scope','runtime:source-task.close']);
24
+ if(!integrationOperations||!workspace?.registerSettlement)throw fail('The source task needs native integration and settlement ports.');
25
+ const ownNames=new Set(['SourceDeliver','SourceRevise','SourceStatus','SourceVerify','SourceEvidence']),publicNames=new Set(['SourceDeliver','SourceRevise','SourceStatus','SourceEvidence','EvidenceRead']);
26
+ // Work cards have smaller display bounds than the accepted 4,000-character contract.
27
+ // Reference the complete immutable criterion; never truncate or replace its acceptance.
28
+ const contractReference='Implement the complete immutable accepted source task (SHA256 '+taskDigest+'). The full criterion is available through SourceStatus; this card is only its work projection.',criterionLength=Array.from(brief.criterion).length;
29
+ const contract={id:'source-task',title:'Implement and verify accepted source contract',criterion:criterionLength>1000?contractReference:brief.criterion,capabilities:['workspace.git'],checks:[{step_id:'combined-source',operation:'SourceVerify',arguments:{},assertions:[{kind:'json_equals',pointer:'/status',value:'verified'}]}]};
30
+ const state=async({historical=false}={})=>{const s=await store.load(sessionId),a=savedSourceTask(s),inspection=inspectWork(s,house.stateContext());if(!s.journalAudit.verified||inspection.audit.violations.length||!sourceTaskFactValid(a)||a.task_digest!==taskDigest||a.binding.reset_ref!==s.rows.findLastIndex(r=>r.type==='reset'))throw fail('The accepted source task/reset scope changed.','SOURCE_TASK_SCOPE_CHANGED');const execution=s.rows.find(r=>r.type==='source_task_execution_bound'&&r.source===source&&r.task_digest===taskDigest);if(!historical&&execution&&execution.goal_owner!==(s.goal?.status==='active'?s.goal.id:null))throw fail('The original active source execution goal changed.','SOURCE_TASK_SCOPE_CHANGED');sourceTaskRevisions(s.rows,a);const work=inspection.work;if(work){const defs=s.rows[work.plan.ref]?.tasks,definition=defs?.[0]&&Object.fromEntries(Object.entries(defs[0]).filter(([k])=>!['definition_digest','work_digest'].includes(k)));if(work.plan.version!==1||defs.length!==1||digest(definition)!==digest({...contract,depends_on:[]}))throw fail('The accepted source work/check definition changed.');}return s;};
31
+ const inputForEditors={tasks:brief.editors.map(e=>({prompt:e.prompt,description:e.id,isolation:'git',git_source:brief.source,work_tracking:'parent',...(e.max_turns?{max_turns:e.max_turns}:{})}))};
32
+ const stages=s=>s.rows.filter(r=>r.type==='source_task_stage'&&r.source===source&&r.task_digest===taskDigest&&r.session_id===sessionId);
33
+ const preparedStage=(s,key)=>stages(s).find(r=>r.key===key);
34
+ const proposal=(s,stage)=>s.rows.find(r=>r.type==='house_proposal'&&r.call_id===stage.call_id&&r.operation===stage.operation&&r.arguments_digest===stage.arguments_digest&&digest(r.actor)===digest(actor));
35
+ const stageReceipt=(s,stage)=>{const p=proposal(s,stage);return p&&s.rows.findLast(r=>r.type==='house_receipt'&&r.invocation_id===p.invocation_id);};
36
+ const evidenceCall=call=>{const {integration_id,...input}=call.input;return{name:'SourceEvidence',input};};
37
+ const projectEvidence=(items,generation)=>items?.map(item=>({...item,read_call:{...evidenceCall(item.read_call),input:{...evidenceCall(item.read_call).input,generation}}}))??[];
38
+ const parse=r=>{if(r?.status!=='returned'||r.output_truncated||r.output_digest!==digest(r.output))throw fail('The original native stage did not return a complete durable outcome.','SOURCE_TASK_STAGE_CLOSED',{receipt_id:r?.invocation_id,code:r?.code??null});return JSON.parse(r.output);};
39
+ const recordCall=async(name,input,o)=>o.recordWork({type:'source_task_call_prepared',source,operation:name,arguments_digest:digest(input),task_digest:taskDigest});
40
+ const frame=async(o,options)=>{
41
+ if(o.sessionId!==sessionId||digest(o.actor)!==digest(actor))throw fail('The accepted source task belongs to another actor/session.','SOURCE_TASK_OWNER');
42
+ const s=await state(options),observed=await o.revalidate(),proposal=s.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===o.invocation_id),contract=proposal&&s.rows.findLast(r=>r.type==='house_contract'&&r.contract_digest===proposal.contract_digest)?.contract;
43
+ if(observed.invocation_id!==o.invocation_id||proposal?.operation!==o.command_name||contract?.origin!==null||contract.revision!==SOURCE_TASK_MECHANISM)throw fail('An admitted native source coordinator frame is required.','SOURCE_TASK_FRAME_REQUIRED');
44
+ if(['SourceDeliver','SourceRevise'].includes(o.command_name)){const id=await integration();if(id){const original=await integrationOperations.preparation(o,id),chain=await integrationOperations.lineage(o,original);await integrationOperations.frame(o,chain);}}
45
+ };
46
+ const stage=async(key,name,input,o)=>{
47
+ let s=await state(),saved=preparedStage(s,key);if(saved&&(saved.operation!==name||saved.arguments_digest!==digest(input)||digest(saved.input)!==digest(input)))throw fail('A source stage cannot replace its original native intent.');
48
+ if(!saved){await o.recordWork({type:'source_task_stage',source,task_digest:taskDigest,key,operation:name,input,arguments_digest:digest(input),call_id:randomUUID()});s=await state();saved=preparedStage(s,key);}
49
+ const p=proposal(s,saved);if(p){const r=stageReceipt(s,saved);if(!r)throw fail('A source stage has an unsettled native dispatch. Recover the same session; no effect was replayed.','SOURCE_TASK_UNCERTAIN');return{value:r.status==='returned'?parse(r):null,receipt:r,proposal:p,stage:saved};}
50
+ await frame(o);if((key==='editors'||name==='Agent')&&(await inspectGitSource({cwd,signal:o.signal})).source_identity!==savedSourceTask(await state()).binding.source_identity)throw fail('The accepted parent source changed before editor launch.','SOURCE_TASK_STALE');await o.recordWork({type:'source_task_dispatch',source,task_digest:taskDigest,stage_ref:s.rows.indexOf(saved),stage_digest:saved.audit.sha256,key});
51
+ try{await o.invoke(name,input,{signal:o.signal,callId:saved.call_id,beforeEffect:()=>frame(o)});}catch(error){if(!['GIT_MERGE_CONFLICT','GIT_NATIVE_CHECK_FAILED'].includes(error.code))throw error;}s=await state();const r=stageReceipt(s,saved);return{value:r?.status==='returned'?parse(r):null,receipt:r,proposal:proposal(s,saved),stage:saved};
52
+ };
53
+ // Retry only a lost handler reply, preserving the native intent/attempt. The
54
+ // kernel remains responsible for refusing an uncertain mutation or check.
55
+ const recoverStage=async(key,name,input,o,{failure=false}={})=>{
56
+ for(let n=0;;n++){
57
+ const value=await stage(n?key+'-recovered-'+n:key,name,input,o);
58
+ if(value.value||failure&&['GIT_MERGE_CONFLICT','GIT_NATIVE_CHECK_FAILED'].includes(value.receipt.code))return value;
59
+ if(!value.receipt.recovery&&!['HOUSE_RECOVERY_NOT_STARTED','HOUSE_RECOVERY_NOT_DISPATCHED'].includes(value.receipt.code))throw fail('The original native stage is closed; no fresh intent was substituted.','SOURCE_TASK_STAGE_CLOSED',{receipt_id:value.receipt.invocation_id});
60
+ }
61
+ };
62
+ const revisions=s=>sourceTaskRevisions(s.rows,savedSourceTask(s));
63
+ const integration=async(options={})=>{
64
+ const s=await state(options),last=options.generation??revisions(s).at(-1)?.binding.generation??0,candidates=options.generation!==undefined?[last]:Array.from({length:last+1},(_,i)=>last-i),owner=digest({actor,session_id:sessionId});
65
+ for(const generation of candidates){const request_id=generation?correctionRequest(taskDigest,generation):'source-'+taskDigest.slice(0,40),intent=s.rows.find(row=>row.type==='git_integration_intent'&&row.operation==='GitIntegrationPrepare'&&row.input.request_id===request_id&&row.owner_digest===owner);if(intent)return intent.binding.resolved.integration_id;}
66
+ return null;
67
+ };
68
+ const generationOf=async id=>{const s=await state({historical:true}),intent=s.rows.find(r=>r.type==='git_integration_intent'&&r.operation==='GitIntegrationPrepare'&&r.binding.resolved.integration_id===id);return revisions(s).find(r=>correctionRequest(taskDigest,r.binding.generation)===intent?.input.request_id)?.binding.generation??0;};
69
+ const inspect=async(o,{invoke=true,historical=false}={})=>{
70
+ const id=await integration({historical});if(!id)return{status:'not_prepared',task_digest:taskDigest,asked:'unjudged'};
71
+ if(!invoke)throw fail('A native observation context is required.');
72
+ const result=await integrationOperations.status({integration_id:id},o),status=result.integrations[0];
73
+ const kernel=new GitWorkspaces({cwd}),resource=await kernel.status(id,{owner:{actor,session_id:sessionId}}),prepared=resource.completed.find(r=>r.action==='integration_prepare')?.output;
74
+ let target=null,current=null;if(prepared){target=await kernel.rootRef('refs/comal/integrations/'+brief.target,o.signal);current=await inspectGitSource({cwd:prepared.worktree,signal:o.signal});}
75
+ const rootSource=await inspectGitSource({cwd,signal:o.signal});
76
+ const valid=rootSource.source_identity===savedSourceTask(await state({historical})).binding.source_identity&&resource.lifecycle==='retained'&&!resource.pending.length&&status.verification==='passed_declared_checks'&&status.applied===status.candidate&&target===status.candidate&&current?.head===status.candidate&&!current.dirty&&current.source_identity===prepared.source_identity;
77
+ o.observe?.({kind:'source_task_ref_observation',task_digest:taskDigest,integration_id:id,target_ref:'refs/comal/integrations/'+brief.target,observed_commit:target,candidate:status.candidate,source_identity:current?.source_identity??null});
78
+ return{status:valid?'verified':'needs_verification',task_digest:taskDigest,integration_id:id,candidate:status.candidate,target_ref:'refs/comal/integrations/'+brief.target,observed_commit:target,verification:status.verification,pending:status.pending,rejected:status.rejected,evidence:projectEvidence(status.evidence,await generationOf(id)),conflicts:status.conflicts,scope:'native_source_and_declared_checks_not_human_acceptance',asked:'unjudged'};
79
+ };
80
+ const locks=new Map();
81
+ const lock=async o=>{const lease=await acquireProcessLock(path.join(store.dir,'.source-task-'+digest({sessionId,actor,taskDigest})+'.lock'),{resource:{session_id:sessionId,invocation_id:o.invocation_id},validate:async old=>{const s=await state();if(old.resource?.session_id!==sessionId||!s.rows.some(r=>r.type==='house_receipt'&&r.invocation_id===old.resource.invocation_id))throw fail('Recover the original coordinator before continuing.','SOURCE_TASK_UNCERTAIN');}}).catch(error=>{if(error.code==='SESSION_IN_USE')throw fail('The accepted source pipeline is already running.','SOURCE_TASK_BUSY');throw error;});locks.set(o.invocation_id,lease);};
82
+ const settle=async({invocation_id,receiptRecorded})=>{const lease=locks.get(invocation_id);if(!lease)return;locks.delete(invocation_id);if(receiptRecorded)await lease.release();else await lease.file.close();};
83
+ const enter=async o=>{
84
+ if(policy.mode==='plan')throw fail('Plan mode cannot start source effects.','HOUSE_REFUSED');await lock(o);await frame(o);const entered=await state();
85
+ if(!entered.rows.some(r=>r.type==='source_task_execution_bound'&&r.source===source&&r.task_digest===taskDigest))await o.recordWork({type:'source_task_execution_bound',source,task_digest:taskDigest,goal_owner:entered.goal?.status==='active'?entered.goal.id:null});
86
+ if(workProjection(await state(),house.stateContext()).counts.done===1){const observed=JSON.parse(await o.invoke('SourceVerify',{}, {signal:o.signal}));if(observed.status!=='verified')throw fail('The recorded delivery is historical; source or target changed.','SOURCE_TASK_STALE');return{...observed,status:'done',reused:true};}
87
+ };
88
+ const correctionState=async(id,o)=>{
89
+ const status=(await integrationOperations.status({integration_id:id},o)).integrations[0],kernel=new GitWorkspaces({cwd});let descriptor,error;
90
+ try{descriptor=await kernel.revisionSource(id,{owner:{actor,session_id:sessionId},signal:o.signal});}catch(e){error=e;}
91
+ return{status,descriptor,error};
92
+ };
93
+ // A proposed next action, never authorization or a task-specific answer. The
94
+ // native editor receives the frozen source, full criterion and failure data.
95
+ const correctionCall=()=>({name:'SourceRevise',input:{instructions:'Inspect the frozen failed source and supplied native diagnostics. Diagnose and correct the observed failure under the complete accepted task; preserve other components and the original verification contract and target.'}});
96
+ const diagnosisAction='The correction editor can read the frozen failed source and diagnose the supplied native failure. Use correction_call to delegate diagnosis and correction, or supply your own SourceRevise instructions. Original criteria, checks, target and permissions remain effective.';
97
+ const editorAttention=(editors,generation)=>({status:'needs_editor_attention',task_digest:taskDigest,...(generation===undefined?{}:{generation}),editors,...(editors.some(r=>['paused','interrupted'].includes(r.status))?{resume_call:{name:'SourceDeliver',input:{resume_editors:true}}}:{}),next_action:'Inspect the original editor outcome. If resume_call is present, it resumes only the saved paused/interrupted editors; no new editor is created. This advice grants no permission.',asked:'unjudged'});
98
+ const failedSource=async(id,o,code)=>{
99
+ const {status,descriptor,error}=await correctionState(id,o);
100
+ if(!descriptor)throw fail('This native outcome is not a known source failure; no correction or verification was replayed.',code??error.code,{native_code:error.code,evidence:projectEvidence(status.evidence,await generationOf(id))});
101
+ const generation=await generationOf(id);
102
+ return{status:'needs_correction',task_digest:taskDigest,generation,kind:descriptor.kind,evidence:projectEvidence(status.evidence,generation),conflicts:status.conflicts,...(generation<31?{correction_call:correctionCall()}:{}),next_action:generation<31?diagnosisAction:'The original bounded correction lineage is full; no further correction is offered.',asked:'unjudged'};
103
+ };
104
+ const finishCandidate=async(prefix,id,input,o)=>{
105
+ let status=JSON.parse(await o.invoke('GitIntegrationStatus',{integration_id:id},{signal:o.signal})).integrations[0];
106
+ if(status.verification!=='passed_declared_checks'){
107
+ const failed=status.rejected.filter(r=>r.action==='integration_verify').length;if(failed&&!input.retry_checks)return failedSource(id,o);
108
+ const number=failed?String(failed):'first',attempt_id='source-check-'+number,check=await recoverStage(prefix+'verify-'+number,'GitIntegrationVerify',{integration_id:id,attempt_id},o,{failure:true});if(!check.value)return failedSource(id,o,check.receipt.code);
109
+ }
110
+ await fault('verified',{o,integration_id:id});status=JSON.parse(await o.invoke('GitIntegrationStatus',{integration_id:id},{signal:o.signal})).integrations[0];if(status.verification!=='passed_declared_checks')throw fail('No passed native combined verification exists.','SOURCE_TASK_CHECK_FAILED');
111
+ if(!status.applied)await recoverStage(prefix+'apply','GitIntegrationApply',{integration_id:id,attempt_id:'source-apply'},o);
112
+ await fault('applied',{o,integration_id:id});const observed=await inspect(o);if(observed.status!=='verified')throw fail('Current source/ref differs from the passed native candidate.','SOURCE_TASK_STALE');
113
+ const work=workProjection(await state(),house.stateContext());if(work.counts.done!==1){const check=await o.invoke('WorkStepRun',{task_id:'source-task',plan_version:1,step_id:'combined-source',recheck:true},{signal:o.signal});await fault('close_check_returned',{o,check});await o.invoke('WorkTaskUpdate',{task_id:'source-task',plan_version:1,status:'done'},{signal:o.signal});}
114
+ await fault('closed',{o});return{...observed,status:'done',acceptance:'unjudged'};
115
+ };
116
+ const useRevision=async(row,o)=>{await o.recordWork({type:'source_task_revision_used',source,task_digest:taskDigest,generation:row.binding.generation,revision_digest:row.audit.sha256});};
117
+ // A recovered/missing coordinator return is not an acknowledgement of a new
118
+ // failure. Continue that generation once before selecting another editor.
119
+ const revisionAcknowledged=(s,row)=>Boolean(row&&s.rows.some(used=>used.type==='source_task_revision_used'&&used.source===source&&used.task_digest===taskDigest&&used.revision_digest===row.audit.sha256&&s.rows.some(r=>r.type==='house_receipt'&&r.invocation_id===used.invocation_id&&r.status==='returned'&&JSON.parse(r.output).status==='needs_correction')));
120
+ const continueRevision=async(row,input,o)=>{
121
+ const b=row.binding,prefix='revision-'+b.generation+'-';await useRevision(row,o);let s=await state(),launch=preparedStage(s,prefix+'editor'),p=launch&&proposal(s,launch),r=launch&&stageReceipt(s,launch);
122
+ if(!p)await stage(prefix+'editor','Agent',b.editor_input,o);
123
+ else if(!r)throw fail('The original correction editor launch is unsettled; recover this session.','SOURCE_TASK_UNCERTAIN');
124
+ else if(r.status!=='returned'){
125
+ if(['HOUSE_RECOVERY_CONTINUE','HOUSE_RECOVERY_NOT_DISPATCHED'].includes(r.code))await recoverStage(prefix+'editor-continue','AgentContinue',{invocation_id:p.invocation_id},o);
126
+ else throw fail('The original correction editor launch was refused; no new editor was substituted.','SOURCE_TASK_EDITORS_CLOSED');
127
+ }
128
+ await subagents.reconcile(sessionId);s=await state();launch=preparedStage(s,prefix+'editor');p=proposal(s,launch);let runs=(await subagents.runs(sessionId)).filter(run=>run.parent_invocation_id===p.invocation_id);
129
+ if(runs.length!==1)throw fail('The correction must retain exactly one original native editor.','SOURCE_TASK_REVISION_BINDING');
130
+ let run=runs[0];if(['paused','interrupted'].includes(run.status)&&input.resume_editors===true){await recoverStage(prefix+'resume-'+run.run_id+'-'+run.turn_id,'AgentResume',{run_id:run.run_id},o);run=(await subagents.runs(sessionId)).find(r=>r.run_id===run.run_id);}
131
+ if(run.status!=='completed'||!run.source_offer)return editorAttention([{run_id:run.run_id,status:run.status,error:run.error,result_read_call:run.result_read_call}],b.generation);
132
+ await fault('revision_editor_returned',{o,run,generation:b.generation});
133
+ const prepared=await recoverStage(prefix+'prepare','GitIntegrationRevise',{request_id:correctionRequest(taskDigest,b.generation),integration_id:b.parent_integration_id,run_id:run.run_id},o,{failure:true});
134
+ const id=await integration();if(!prepared.value)return failedSource(id,o,prepared.receipt.code);
135
+ await fault('revision_prepared',{o,integration_id:id,generation:b.generation});return finishCandidate(prefix,id,input,o);
136
+ };
137
+ const revise=async(input,o)=>{
138
+ if(!await integration())throw fail('Prepare and observe a known native failure through SourceDeliver first.','SOURCE_TASK_NOT_PREPARED');
139
+ const done=await enter(o);if(done)return done;
140
+ let s=await state(),latest=revisions(s).at(-1),id=await integration();if(!id)throw fail('Prepare and observe a known native failure through SourceDeliver first.','SOURCE_TASK_NOT_PREPARED');
141
+ const failed=await correctionState(id,o),sameGeneration=latest&&await generationOf(id)===latest.binding.generation;
142
+ const acknowledged=revisionAcknowledged(s,latest);
143
+ if(latest&&!(sameGeneration&&failed.descriptor&&acknowledged)){
144
+ if(input.instructions!==latest.binding.instructions)throw fail('Continue the original correction instructions before selecting new work. Use SourceStatus for the exact continuation call.','SOURCE_TASK_REVISION_CONFLICT');
145
+ return continueRevision(latest,{},o);
146
+ }
147
+ if(!failed.descriptor)throw fail('Correction requires a known native source/check failure, never a denial or uncertain effect.','SOURCE_TASK_REVISION_NOT_REQUIRED',{native_code:failed.error.code});
148
+ if((await inspectGitSource({cwd,signal:o.signal})).source_identity!==savedSourceTask(s).binding.source_identity)throw fail('The accepted human source changed before correction.','SOURCE_TASK_STALE');
149
+ const generation=(latest?.binding.generation??0)+1;if(generation>31)throw fail('The bounded original correction lineage is full.','SOURCE_TASK_REVISION_LIMIT');
150
+ const diagnostics={kind:failed.descriptor.kind,conflicts:failed.status.conflicts.map(c=>({details:c.details?.slice(0,4000)}))};
151
+ const reference=failed.status.evidence.find(e=>e.operation==='Bash')?.read_call;
152
+ if(reference){const call=evidenceCall(reference),page=JSON.parse(await o.invoke('SourceEvidence',{...call.input,generation:await generationOf(id),limit:4000},{signal:o.signal}));diagnostics.check_output=page.content;diagnostics.evidence={...call.input,generation:await generationOf(id)};}
153
+ const original=await integrationOperations.preparation(o,id);s=await state();const binding={generation,parent_integration_id:id,source:failed.descriptor,instructions:input.instructions,diagnostics,goal_owner:s.rows.find(r=>r.type==='source_task_execution_bound'&&r.task_digest===taskDigest).goal_owner,origin:{row:s.rows.findIndex(r=>r.audit.sha256===original.audit.sha256),digest:original.audit.sha256,binding_digest:original.binding_digest}};binding.editor_input=correctionEditor(brief,binding);
154
+ await o.recordWork({type:'source_task_revision',source,task_digest:taskDigest,binding,binding_digest:digest(binding)});s=await state();latest=revisions(s).at(-1);await fault('revision_selected',{o,generation});return continueRevision(latest,{},o);
155
+ };
156
+ const deliver=async(input,o)=>{
157
+ const done=await enter(o);if(done)return done;
158
+ const latest=revisions(await state()).at(-1);if(latest)return continueRevision(latest,input,o);
159
+ await fault('before_editors',{o});let s=await state(),launch=preparedStage(s,'editors'),batch;
160
+ if(!launch||!proposal(s,launch))batch=await stage('editors','AgentBatch',inputForEditors,o);
161
+ else{const p=proposal(s,launch),r=stageReceipt(s,launch);if(!r)throw fail('The original editor launch is unsettled; recover it before continuing.','SOURCE_TASK_UNCERTAIN');if(r.status==='returned')batch={value:parse(r),receipt:r,proposal:p};else if(['HOUSE_RECOVERY_CONTINUE','HOUSE_RECOVERY_NOT_DISPATCHED'].includes(r.code))batch=await recoverStage('editors-continue','AgentContinue',{invocation_id:p.invocation_id},o);else throw fail('The original editor batch was refused/failed; it was not restarted.','SOURCE_TASK_EDITORS_CLOSED');}
162
+ await subagents.reconcile(sessionId);s=await state();launch=preparedStage(s,'editors');const parent=proposal(s,launch),runs=(await subagents.runs(sessionId)).filter(run=>run.parent_invocation_id===parent.invocation_id);
163
+ for(const run of runs)if(['paused','interrupted'].includes(run.status)&&input.resume_editors===true){const resumed=await recoverStage('resume-'+run.run_id+'-'+run.turn_id,'AgentResume',{run_id:run.run_id},o);if(!resumed.value)throw fail('The original editor resume is closed; inspect its native outcome.','SOURCE_TASK_STAGE_CLOSED');}
164
+ const current=(await subagents.runs(sessionId)).filter(run=>run.parent_invocation_id===parent.invocation_id);if(current.length!==brief.editors.length||current.some(run=>run.status!=='completed'||!run.source_offer))return editorAttention(current.map(r=>({run_id:r.run_id,status:r.status,turn_state:r.turn_state,error:r.error,result_read_call:r.result_read_call})));
165
+ await fault('editors_returned',{o,runs:current});
166
+
167
+ const prepInput={request_id:'source-'+taskDigest.slice(0,40),target:brief.target,run_ids:current.map(r=>r.run_id),verification:brief.verification},prep=await recoverStage('prepare','GitIntegrationPrepare',prepInput,o,{failure:true}),id=await integration();
168
+ if(!prep.value)return failedSource(id,o,prep.receipt.code);await fault('prepared',{o,integration_id:id});return finishCandidate('',id,input,o);
169
+ };
170
+ const revisionView=async(observed)=>{
171
+ const s=await state({historical:true}),items=revisions(s),latest=items.at(-1),id=await integration({historical:true});let failure;
172
+ if(id){try{failure=await new GitWorkspaces({cwd}).revisionSource(id,{owner:{actor,session_id:sessionId}});}catch(error){if(!['GIT_REVISION_NOT_REQUIRED','GIT_REVISION_UNCERTAIN','GIT_RESOURCE_RETIRED'].includes(error.code))throw error;}}
173
+ const acknowledged=revisionAcknowledged(s,latest),generation=await generationOf(id),continuing=observed.status!=='verified'&&latest&&!(generation===latest.binding.generation&&failure&&acknowledged),canCorrect=failure&&!continuing&&generation<31;
174
+ return{available:Boolean(failure),current_generation:generation,generations:items.map(r=>({generation:r.binding.generation,kind:r.binding.source.kind})),...(continuing?{continue_call:{name:'SourceRevise',input:{instructions:latest.binding.instructions}}}:canCorrect?{correction_call:correctionCall()}:{}),next_action:observed.status==='verified'?'Native delivery is verified; human acceptance remains unjudged.':continuing?'Continue the exact saved correction with continue_call; do not select another editor. SourceDeliver can explicitly resume a paused editor.':canCorrect?diagnosisAction:failure?'The original bounded correction lineage is full; no further correction is offered.':'SourceDeliver continues any saved editor/check/ref stages. Denial or uncertainty does not authorize a source correction.'};
175
+ };
176
+ const publicWork=book=>{const w=workProjection(book,house.stateContext());return{counts:w.counts,cards:w.cards.map(({title,status})=>({title,status})),current:w.current?{title:w.current.title,status:w.current.status,criterion:w.current.criterion}:null,close_call:{name:'SourceDeliver',input:{}},administrative_controls:'host_owned',permissions_from_plan:false};};
177
+ const evidenceSchema=structuredClone(gitIntegrationDefinitions.find(d=>d.name==='GitIntegrationEvidence').input_schema);delete evidenceSchema.properties.integration_id;evidenceSchema.required=evidenceSchema.required.filter(k=>k!=='integration_id');evidenceSchema.properties.generation={type:'integer',minimum:0,maximum:31};
178
+ const defs=[{name:'SourceRevise',description:'Correct a known failed accepted source using one new native editor and the ORIGINAL checks/target. The editor can read the frozen failed source, inspect native diagnostics and diagnose the defect itself; you do not need to identify an application fix first. Use a supplied correction_call or your own source correction instructions, not replacement criteria. Continue a lost/unfinished correction with its exact saved instructions; SourceDeliver can resume its paused editor. House owns all generation/run/check/ref IDs. A known failed correction may be revised again after its outcome is recorded.',input_schema:{type:'object',required:['instructions'],properties:{instructions:{type:'string',minLength:1,maxLength:12000}},additionalProperties:false},run:revise},{name:'SourceDeliver',description:'Continue the SAME accepted source task. The house owns editor/check/ref/work identities. Native AgentBatch editors implement source, then native combined checks and reserved-ref CAS establish closure. resume_editors explicitly resumes paused/interrupted originals; retry_checks explicitly retries known failed checks. Never rewrites criteria or restarts an uncertain batch.',input_schema:{type:'object',properties:{resume_editors:{type:'boolean'},retry_checks:{type:'boolean'}},additionalProperties:false},run:deliver},{name:'SourceEvidence',description:'Page exact native combined-check evidence for THIS accepted task, including failed/uncertain outcomes. Use the read_call supplied by SourceStatus. No owner, integration ID, permission or success override.',input_schema:evidenceSchema,run:async(input,o)=>{const {generation,...query}=input;const id=await integration({historical:true,...(generation!==undefined?{generation}:{} )});if(generation!==undefined&&await generationOf(id)!==generation)throw fail('No verifier exists for the selected original source generation.','SOURCE_TASK_NOT_PREPARED');if(!id)throw fail('No native combined verifier exists yet.','SOURCE_TASK_NOT_PREPARED');const result=await integrationOperations.evidence({...query,integration_id:id},o);if(result.next_call){result.next_call=evidenceCall(result.next_call);result.next_call.input.generation=await generationOf(id);}return result;}},...['SourceStatus','SourceVerify'].map(name=>({name,description:name==='SourceStatus'?'Read accepted source-task state, original editor outcomes and native evidence; no polling or permission grant.':'Host-only declared check: observe passed native combined checks, current candidate source and actual reserved target. No model success flag.',input_schema:empty,run:async(_,o)=>{if(name==='SourceVerify')return inspect(o);const observed=await inspect(o,{historical:true});return{task:brief,task_digest:taskDigest,work:publicWork(await state({historical:true})),editors:(await subagents.runs(sessionId)).map(r=>({run_id:r.run_id,status:r.status,source_offer:r.source_offer,error:r.error})),integration:observed,correction:await revisionView(observed)};}}))];
179
+ house.registry.replace([...ownNames],defs.map(d=>({...d,revision:SOURCE_TASK_MECHANISM,steps:['SourceDeliver','SourceRevise'].includes(d.name),permission:['SourceDeliver','SourceRevise'].includes(d.name)?d.name:d.name==='SourceEvidence'?'GitIntegrationEvidence':'GitIntegrationStatus',effects:['SourceDeliver','SourceRevise'].includes(d.name)?{workspace:'unknown',external:'unknown',process:'unknown',authority:'none'}:{workspace:'read',external:'none',process:'read',authority:'none'},run:async(input,o)=>{o={...o,command_name:d.name};await recordCall(d.name,input,o);await frame(o,{historical:['SourceStatus','SourceEvidence'].includes(d.name)});const value=await d.run(input,o);if(['SourceDeliver','SourceRevise'].includes(d.name))await frame(o);return JSON.stringify(value);}})));
180
+ if(accepted)await workspace.recoverInterrupted();
181
+ let setupTurn;
182
+ if(house.registry.get('ToolPack')){setupTurn=randomUUID();await store.append(sessionId,{type:'tool_turn',source:'capability_host',actor,id:setupTurn,open:true});await executor.execute('ToolPack',{action:'acquire',pack:'workspace.git'});}
183
+ try{
184
+ if(!accepted){const observed=JSON.parse(await executor.execute('GitInspect',{}));const binding={cwd,session_id:sessionId,actor,goal_id:goal,reset_ref:epoch,source_identity:observed.source_identity};accepted=await store.append(sessionId,{type:'source_task_accepted',source,mechanism:SOURCE_TASK_MECHANISM,house:cwd,session_id:sessionId,actor,surface:'agent',task:brief,task_digest:taskDigest,governance_plan_hash:plan.plan_hash,binding,binding_digest:digest(binding)});}
185
+ if(!workProjection(await store.load(sessionId)))await executor.execute('WorkPlanSet',{objective:criterionLength>2200?contractReference:brief.criterion,expected_version:0,tasks:[contract]});
186
+ if(workProjection(await store.load(sessionId)).current?.status==='pending')await executor.execute('WorkTaskUpdate',{task_id:'source-task',plan_version:1,status:'in_progress'});
187
+ if(house.registry.get('ToolPack'))await executor.execute('ToolPack',{action:'acquire',pack:'workspace.git'});
188
+ }finally{if(setupTurn)await store.append(sessionId,{type:'tool_turn',source:'capability_host',actor,id:setupTurn,open:false});}
189
+ const stageBound=(v,s)=>{
190
+ const p=s.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===v.invocation_id);
191
+ return p&&stages(s).some(stage=>stage.operation===v.operation&&stage.call_id===p.call_id&&stage.arguments_digest===digest(v.input)&&digest(stage.input)===digest(v.input));
192
+ };
193
+ const scope=async v=>{
194
+ if(v.actor.kind==='human')return true;let s;try{s=await state({historical:['SourceStatus','SourceEvidence'].includes(v.operation)&&v.session_id===sessionId});}catch(error){return{allowed:false,reason:error.message};}
195
+ if(v.session_id!==sessionId)return true;if(digest(v.actor)!==digest(actor))return false;
196
+ const coordinator=['SourceDeliver','SourceRevise'].includes(v.parent_operation);
197
+ if(ownNames.has(v.operation))return v.operation!=='SourceVerify'||['WorkStepRun','SourceDeliver','SourceRevise'].includes(v.parent_operation);
198
+ if(v.operation==='EvidenceRead')return true;
199
+ if(['Agent','AgentBatch','AgentContinue','AgentResume','GitIntegrationRevise','GitIntegrationPrepare','GitIntegrationVerify','GitIntegrationApply'].includes(v.operation)){
200
+ if(coordinator&&stageBound(v,s))return true;
201
+ if(v.operation==='GitIntegrationPrepare'&&v.parent_operation==='GitIntegrationRevise'){
202
+ const parent=s.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===v.parent_invocation_id),stage=parent&&stages(s).find(r=>r.operation==='GitIntegrationRevise'&&r.call_id===parent.call_id&&r.arguments_digest===parent.arguments_digest);
203
+ return Boolean(stage&&coordinatorParent(s,parent)&&digest(v.input)===digest({request_id:stage.input.request_id,target:brief.target,run_ids:[stage.input.run_id],verification:brief.verification,revision_of:stage.input.integration_id}));
204
+ }
205
+ return false;
206
+ }
207
+ if(v.operation==='GitIntegrationStatus')return ['SourceDeliver','SourceRevise','SourceVerify','SourceStatus'].includes(v.parent_operation);
208
+ if(v.operation==='WorkStepRun')return coordinator&&digest(v.input)===digest({task_id:'source-task',plan_version:1,step_id:'combined-source',recheck:true});
209
+ if(v.operation==='WorkTaskUpdate')return coordinator&&digest(v.input)===digest({task_id:'source-task',plan_version:1,status:'done'});
210
+ return{allowed:false,reason:'This accepted v2 source task uses bounded native source/correction actions; generic administration remains unavailable.'};
211
+ };
212
+ const coordinatorParent=(s,p)=>s.rows.some(r=>r.type==='house_proposal'&&r.invocation_id===p.parent_invocation_id&&['SourceDeliver','SourceRevise'].includes(r.operation)&&digest(r.actor)===digest(actor));
213
+ const close=async v=>{if(v.session_id!==sessionId||v.actor.kind==='human'||v.input.status!=='done')return true;const s=await state(),r=s.rows.findLast(row=>row.type==='house_receipt'&&row.operation==='SourceVerify'&&row.status==='returned');if(!r||JSON.parse(r.output).status!=='verified')return false;const value=JSON.parse(r.output),kernel=new GitWorkspaces({cwd});return await kernel.rootRef(value.target_ref)===value.candidate;};
214
+ if(accepted.governance_plan_hash&&accepted.governance_plan_hash!==plan.plan_hash)throw fail('The saved source workflow policy changed.');
215
+ await house.activateGovernance({sessionId,plan,bindings:{'runtime:source-task.scope':{revision:SOURCE_TASK_MECHANISM,operations:['*'],configuration_digest:taskDigest,check:scope},'runtime:source-task.close':{revision:SOURCE_TASK_MECHANISM,operations:['WorkTaskUpdate'],configuration_digest:taskDigest,check:close}}});
216
+ const retireSettlement=workspace.registerSettlement(settle);
217
+ const project=snapshot=>{const definitions=snapshot.definitions().filter(d=>publicNames.has(d.name)),offered=new Set(definitions.map(d=>d.name));return{...snapshot,definitions:()=>definitions,execute:(name,input,options={})=>{if(options.modelProposed&&!offered.has(name))throw fail('The house owns accepted source bookkeeping; use the offered source actions.','SOURCE_TASK_SCOPE');return snapshot.execute(name,input,options);}};};
218
+ return{task:brief,taskDigest,system:'Accepted source task: '+JSON.stringify(brief)+'. Use SourceDeliver to implement through the original native editors, combine, verify and close. SourceRevise corrects known source/check failures under the same accepted criteria. The correction editor can inspect the frozen failed source and diagnose supplied native failure data; you may delegate diagnosis with correction_call without identifying an application fix yourself. Use continue_call for a saved correction and resume_call for paused editors. SourceStatus/SourceEvidence are optional inspection when needed, not prerequisites for delegating diagnosis. SourceDeliver continues a saved correction; do not invent bookkeeping. SourceStatus/SourceEvidence/EvidenceRead inspect missing failure evidence. The house derives IDs and performs declared verification. Never create generic work plans, administer Git, change criteria or infer acceptance. Report limits on refusal/uncertainty.',dispose:()=>{retireSettlement();house.retireGovernance(sessionId);house.registry.replace([...ownNames],[]);},wrap:tools=>({...tools,house,policy,workflow:'source-task',definitions:()=>project(tools.snapshot()).definitions(),snapshot:()=>project(tools.snapshot()),execute:(n,i,o)=>project(tools.snapshot()).execute(n,i,o)}),settle,
219
+ async finishResult(final,context){
220
+ let delivery={status:'open',task_digest:taskDigest,asked:'unjudged',scope:'accepted_source_and_declared_checks_not_human_acceptance'};
221
+ const s=await state({historical:true}),closed=s.rows.slice(context.baseline).findLast(r=>r.type==='house_receipt'&&['SourceDeliver','SourceRevise'].includes(r.operation)&&r.status==='returned'&&JSON.parse(r.output).status==='done');
222
+ if(!final.is_error&&!final.work_stop_reason&&closed){try{await state();const observed=JSON.parse(await executor.execute('SourceStatus',{}, {signal:context.signal}));if(observed.integration.status!=='verified')throw fail('Final native source/ref observation failed.','SOURCE_TASK_STALE');const read=(await state()).rows.findLast(r=>r.type==='house_receipt'&&r.operation==='SourceStatus'&&r.status==='returned');delivery={...delivery,status:'verified',receipt_id:read.invocation_id,candidate:observed.integration.candidate,target_ref:observed.integration.target_ref};}catch(error){delivery={...delivery,status:'needs_verification',code:error.code??'SOURCE_TASK_FINAL_CHECK'};final.is_error=true;final.work_stop_reason='verification_failed';final.result+='\nThe final source/ref check failed; delivery remains unverified.';}}
223
+ else if(!final.is_error){final.is_error=true;final.work_stop_reason??='source_task_open';final.result+='\nThe accepted source delivery remains unverified.';}
224
+ final.delivery=delivery;await store.append(sessionId,{type:'source_task_delivery',source,...delivery});
225
+ },
226
+ async completedResult(calls=[]){const s=await state(),r=s.rows.findLast(r=>r.type==='house_receipt'&&['SourceDeliver','SourceRevise'].includes(r.operation)&&r.status==='returned'&&calls.some(c=>c.id===r.call_id&&c.name===r.operation));if(!r||workProjection(s,house.stateContext()).counts.done!==1)return null;const value=JSON.parse(r.output);if(value.status!=='done')return null;return{terminal:true,receipt_id:r.invocation_id,result:`Source candidate verified at ${value.target_ref}: ${value.candidate}. Accepted combined checks passed; evidence ${r.invocation_id}. Human acceptance remains unjudged.`};}};
227
+ }
@@ -0,0 +1,13 @@
1
+ import {savedSourceTask,SOURCE_TASK_MECHANISM,SOURCE_TASK_MECHANISM_V2} from '../source-task-proof.mjs';
2
+ import {prepareSourceTaskV1} from './source-task-v1.mjs';
3
+ import {prepareSourceTaskV2} from './source-task-v2.mjs';
4
+ export {sourceTaskSchema,normalizeSourceTask,readSourceTask} from './source-task-v1.mjs';
5
+
6
+ // A saved mechanism selects its original implementation. New sessions bind v2;
7
+ // startup does not migrate an accepted v1 scope or silently grant new actions.
8
+ export async function prepareSourceTask(options){
9
+ const accepted=savedSourceTask(await options.store.load(options.sessionId));
10
+ if(accepted?.mechanism===SOURCE_TASK_MECHANISM)return prepareSourceTaskV1(options);
11
+ if(accepted&&accepted.mechanism!==SOURCE_TASK_MECHANISM_V2)throw Object.assign(new Error('Unsupported accepted source-task mechanism; no workflow was migrated.'),{code:'SOURCE_TASK_VERSION'});
12
+ return prepareSourceTaskV2(options);
13
+ }