@milpa/comal 0.1.7 → 0.2.1
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.
- package/README.md +19 -1
- package/backend/contracts/remote-commands-v1.json +12 -0
- package/governance/chrome-task-v10.json +323 -0
- package/governance/chrome-task-v11.json +355 -0
- package/governance/chrome-task-v12.json +387 -0
- package/governance/chrome-task-v13.json +419 -0
- package/governance/chrome-task-v3.json +99 -0
- package/governance/chrome-task-v4.json +131 -0
- package/governance/chrome-task-v5.json +163 -0
- package/governance/chrome-task-v6.json +195 -0
- package/governance/chrome-task-v7.json +227 -0
- package/governance/chrome-task-v8.json +259 -0
- package/governance/chrome-task-v9.json +291 -0
- package/governance/source-task-v1.json +1 -0
- package/governance/source-task-v2.json +1 -0
- package/package.json +14 -1
- package/src/agent-recovery-proof.mjs +12 -1
- package/src/agent.mjs +94 -30
- package/src/audit.mjs +21 -6
- package/src/auto-guard.mjs +48 -1
- package/src/chrome-candidate-recovery-proof.mjs +47 -0
- package/src/chrome-recovery-proof.mjs +6 -2
- package/src/chrome-source-syntax.mjs +93 -0
- package/src/cli.mjs +158 -54
- package/src/codex-provider.mjs +3 -2
- package/src/connection-settings.mjs +204 -0
- package/src/context-pressure.mjs +2 -1
- package/src/durable-agent-runs.mjs +5 -11
- package/src/event-channels.mjs +8 -0
- package/src/file-recovery-proof.mjs +11 -3
- package/src/file-recovery.mjs +24 -0
- package/src/gemini-provider.mjs +6 -1
- package/src/git-agent-revision.mjs +12 -0
- package/src/git-agent-workspaces.mjs +143 -0
- package/src/git-integration-lineage.mjs +20 -0
- package/src/git-integration-operations.mjs +200 -0
- package/src/git-integration-recovery-proof.mjs +70 -0
- package/src/git-page-cli-bridge.mjs +61 -0
- package/src/git-page-evidence.mjs +30 -0
- package/src/git-page-identity.mjs +10 -0
- package/src/git-page-proof.mjs +96 -0
- package/src/git-page-settlement.mjs +21 -0
- package/src/git-page-verification.mjs +134 -0
- package/src/git-plumbing.mjs +212 -0
- package/src/git-process-worker.mjs +50 -0
- package/src/git-source.mjs +110 -0
- package/src/git-workspaces.mjs +519 -0
- package/src/goals.mjs +13 -7
- package/src/house.mjs +246 -20
- package/src/i18n/cli-es.mjs +4 -0
- package/src/i18n/es.mjs +35 -0
- package/src/judges.mjs +27 -11
- package/src/mcp-runtime.mjs +15 -3
- package/src/model-controls.mjs +11 -1
- package/src/model-deadline.mjs +4 -3
- package/src/native-refusal.mjs +38 -0
- package/src/onboarding.mjs +88 -24
- package/src/openai-provider.mjs +14 -7
- package/src/operations.mjs +9 -2
- package/src/output-recovery.mjs +48 -0
- package/src/peer-tools.mjs +18 -8
- package/src/permission-controls.mjs +232 -0
- package/src/permission-rules.mjs +312 -0
- package/src/permissions.mjs +113 -30
- package/src/plugin-catalog.mjs +84 -0
- package/src/plugin-config.mjs +48 -6
- package/src/plugin-controls.mjs +332 -0
- package/src/plugin-notices.mjs +88 -0
- package/src/plugin-trust.mjs +104 -0
- package/src/plugins/chrome-lab/bridge.mjs +38 -12
- package/src/plugins/chrome-lab/client.mjs +103 -25
- package/src/plugins/chrome-lab/diagnostics.mjs +19 -0
- package/src/plugins/chrome-lab/index.mjs +116 -26
- package/src/plugins/chrome-lab/project.mjs +30 -2
- package/src/plugins/chrome-lab/scoped-input.mjs +135 -0
- package/src/plugins/chrome-lab/worker-diagnostics.mjs +51 -0
- package/src/plugins/open-relay/actions.mjs +205 -0
- package/src/plugins/open-relay/bound.mjs +47 -0
- package/src/plugins/open-relay/client.mjs +158 -0
- package/src/plugins/open-relay/dossier.mjs +119 -0
- package/src/plugins/open-relay/index.mjs +153 -0
- package/src/plugins/open-relay/lookup.mjs +45 -0
- package/src/plugins/open-relay/notices.mjs +79 -0
- package/src/plugins/open-relay/watch.mjs +127 -0
- package/src/plugins.mjs +72 -29
- package/src/private-fs.mjs +46 -0
- package/src/process-lock.mjs +24 -13
- package/src/provider-auth.mjs +14 -3
- package/src/provider.mjs +11 -2
- package/src/read-only-check.mjs +7 -3
- package/src/scope-ceiling.mjs +56 -0
- package/src/scoped-policy.mjs +39 -0
- package/src/secret-login.mjs +68 -0
- package/src/session-bus.mjs +41 -7
- package/src/session-projections-candidate.mjs +119 -0
- package/src/session-projections.mjs +119 -0
- package/src/session-read-cache.mjs +23 -3
- package/src/sessions.mjs +35 -11
- package/src/source-editor-role.mjs +13 -0
- package/src/source-ownership.mjs +76 -0
- package/src/source-proposal-proof.mjs +55 -0
- package/src/source-proposals.mjs +118 -0
- package/src/source-requests.mjs +148 -0
- package/src/source-task-proof.mjs +24 -0
- package/src/source-task-revision-proof.mjs +44 -0
- package/src/subagents.mjs +187 -87
- package/src/tool-packs.mjs +5 -5
- package/src/tools.mjs +82 -15
- package/src/trusted-context.mjs +5 -1
- package/src/tui/connection-transition.mjs +1 -1
- package/src/tui/controller.mjs +8 -4
- package/src/tui/discovery.mjs +24 -12
- package/src/tui/inbound-blocks.mjs +55 -0
- package/src/tui/present.mjs +504 -0
- package/src/tui/screen.mjs +49 -13
- package/src/windows-private.ps1 +63 -0
- package/src/work-assertions.mjs +6 -2
- package/src/work-plan.mjs +34 -8
- package/src/work-state.mjs +15 -9
- package/src/workflows/chrome-page-cases.mjs +156 -0
- package/src/workflows/chrome-page-checkpoint-reader.mjs +10 -0
- package/src/workflows/chrome-page-diagnostics.mjs +113 -0
- package/src/workflows/chrome-page-law.mjs +48 -0
- package/src/workflows/chrome-page-recovery-host-v13.mjs +14 -0
- package/src/workflows/chrome-page-recovery-v13.mjs +104 -0
- package/src/workflows/chrome-page-task.mjs +429 -0
- package/src/workflows/chrome-page-verifier.mjs +123 -0
- package/src/workflows/chrome-source.mjs +73 -0
- package/src/workflows/chrome-supplied-inputs.mjs +43 -0
- package/src/workflows/chrome-task-adoption.mjs +122 -0
- package/src/workflows/chrome-task.mjs +379 -74
- package/src/workflows/source-task-v1.mjs +124 -0
- package/src/workflows/source-task-v2.mjs +227 -0
- package/src/workflows/source-task.mjs +13 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Native case ownership/settlement only. No application criteria or browser logic.
|
|
2
|
+
import { digest } from '../operations.mjs';
|
|
3
|
+
import { PAGE_BEHAVIOR_OPERATION, PAGE_VERIFIER_CONTRACT } from './chrome-page-verifier.mjs';
|
|
4
|
+
import { nativePageFailure, NATIVE_PAGE_FAILURE_KINDS } from '../native-refusal.mjs';
|
|
5
|
+
|
|
6
|
+
export const PAGE_CASE_OPERATION = 'ChromePageCase';
|
|
7
|
+
export const PAGE_CASE_LIMITS = Object.freeze({ max_cases: 24, max_behavior_calls: 1600, timeout_ms: 180000, max_summary_bytes: 12000 });
|
|
8
|
+
export const PAGE_CASE_INPUT = Object.freeze({ type: 'object', required: ['phase','index'], additionalProperties: false, properties: { phase: { enum: ['run','validate_final'] }, index: { type: 'integer', minimum: 0, maximum: PAGE_CASE_LIMITS.max_cases } } });
|
|
9
|
+
const fail=(message,code='CHROME_TASK_CASE_EVIDENCE')=>nativePageFailure(message,code);
|
|
10
|
+
const basis=context=>Object.fromEntries(Object.entries(context).filter(([key])=>key!=='case'));
|
|
11
|
+
const returned=(book,id,operation,contract,plan)=>{
|
|
12
|
+
const row=book.rows.find(row=>row.type==='house_receipt'&&row.invocation_id===id);
|
|
13
|
+
const expected=typeof plan==='string'?{plan_hash:plan}:plan;
|
|
14
|
+
if(!row||row.operation!==operation||row.status!=='returned'||row.contract_digest!==contract||row.governance_plan_hash!==expected.plan_hash||typeof plan!=='string'&&(row.mechanism_stamp!==expected.mechanism_stamp||row.governance_activation_id!==expected.activation_id)||row.output_truncated||row.output_digest!==digest(row.output))throw fail('Original complete case/native owner receipt is unavailable.','CHROME_TASK_UNCERTAIN');
|
|
15
|
+
return {row,value:JSON.parse(row.output)};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function createPageCaseCoordinator({cases,taskDigest,plan,store,sessionId,state,save,context,fresh,runBehavior,getOperation,receiptLaw=()=>plan.plan_hash}){
|
|
19
|
+
const names=[...cases],planDigest=digest(names);let caseOperation,behaviorOperation,frame=null;
|
|
20
|
+
const meta=(phase,index)=>({index,id:phase==='run'?names[index]:'$final',plan_digest:planDigest});
|
|
21
|
+
const pending=value=>value.case_pending;
|
|
22
|
+
const book=()=>store.load(sessionId);
|
|
23
|
+
const readCase=(record,journal)=>{
|
|
24
|
+
const {phase,index,owner_id,receipt_id}=record;
|
|
25
|
+
if(!['run','validate_final'].includes(phase)||!Number.isInteger(index)||index<0||index>(phase==='run'?names.length-1:names.length)||phase==='validate_final'&&index!==names.length)throw fail('Case receipt index/phase differs from the accepted plan.');
|
|
26
|
+
if(getOperation(PAGE_CASE_OPERATION)!==caseOperation)throw fail('Original case operation was replaced.');
|
|
27
|
+
const owner=returned(journal,owner_id,PAGE_CASE_OPERATION,caseOperation.digest,receiptLaw(journal,owner_id)),ownerProposal=journal.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===owner_id);
|
|
28
|
+
if(!ownerProposal||ownerProposal.arguments_digest!==digest({phase,index})||owner.value.native_receipt_id!==receipt_id||owner.value.phase!==phase||digest(owner.value.case)!==digest(meta(phase,index)))throw fail('Case owner does not bind the expected native result.');
|
|
29
|
+
const parent=journal.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===ownerProposal.parent_invocation_id);
|
|
30
|
+
if(parent?.operation!=='ChromeTest')throw fail('Case owner did not originate from the native test coordinator.');
|
|
31
|
+
const started=journal.rows.filter(r=>r.type==='chrome_page_case_started'&&r.source==='workflow_host'&&r.task_digest===taskDigest&&r.owner_id===owner_id);
|
|
32
|
+
const observed=journal.rows.filter(r=>r.type==='chrome_page_case_observed'&&r.source==='workflow_host'&&r.task_digest===taskDigest&&r.owner_id===owner_id);
|
|
33
|
+
if(started.length!==1||observed.length!==1||digest(started[0].case)!==digest(meta(phase,index))||observed[0].receipt_id!==receipt_id)throw fail('Original case start/result settlement is missing or ambiguous.','CHROME_TASK_UNCERTAIN');
|
|
34
|
+
if(getOperation(PAGE_BEHAVIOR_OPERATION)!==behaviorOperation)throw fail('Original native behavior operation was replaced.');
|
|
35
|
+
const native=returned(journal,receipt_id,PAGE_BEHAVIOR_OPERATION,behaviorOperation.digest,receiptLaw(journal,receipt_id)),proposal=journal.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===receipt_id);
|
|
36
|
+
if(native.row.parent_invocation_id!==owner_id||proposal?.arguments_digest!==digest(started[0].input)||native.value.phase!==phase||native.value.context_digest!==digest(native.value.context)||digest(native.value.context.case)!==digest(meta(phase,index))||owner.value.status!==native.value.status)throw fail('Case native receipt/input/context chain changed.');
|
|
37
|
+
const input=started[0].input;
|
|
38
|
+
if(input.phase!==phase||digest(input.context.case)!==digest(meta(phase,index))||input.context.task_digest!==taskDigest||digest(input.context.verification.cases)!==planDigest||observed[0].output_digest!==native.row.output_digest)throw fail('Case start does not match the accepted case plan.');
|
|
39
|
+
return {record,input,result:native.value};
|
|
40
|
+
};
|
|
41
|
+
const history=async(value,journal=undefined)=>{
|
|
42
|
+
const checkpoint=pending(value),isPending=value.stage==='case_pending';
|
|
43
|
+
if(Boolean(checkpoint)!==isPending||!isPending&&value.case_cursor!=null)throw fail('Case pending metadata contradicts its durable execution stage.','CHROME_TASK_UNCERTAIN');
|
|
44
|
+
if(isPending&&(!['run','validate_final'].includes(checkpoint.phase)||!Number.isInteger(checkpoint.index)||checkpoint.index<0||checkpoint.index>(checkpoint.phase==='run'?names.length-1:names.length)||checkpoint.phase==='validate_final'&&checkpoint.index!==names.length||typeof checkpoint.owner_id!=='string'||!checkpoint.owner_id||digest(value.case_cursor)!==digest(meta(checkpoint.phase,checkpoint.index))))throw fail('Pending case cursor does not bind its original phase/index owner.','CHROME_TASK_UNCERTAIN');
|
|
45
|
+
journal ||= await book();const records=value.case_progress||[];
|
|
46
|
+
if(!Array.isArray(records)||records.length>names.length)throw fail('Case progress exceeds the accepted plan.');
|
|
47
|
+
const owners=new Set(records.map(r=>r.owner_id)),starts=journal.rows.filter(r=>r.type==='chrome_page_case_started'&&r.task_digest===taskDigest&&r.input?.context?.publication?.attempt_id===value.attempt_id);
|
|
48
|
+
for(const start of starts){
|
|
49
|
+
if(start.owner_id===value.case_pending?.owner_id)continue;
|
|
50
|
+
if(start.input.phase==='run'&&!owners.has(start.owner_id))throw fail('A started case disappeared from its original checkpoint; effects cannot replay.','CHROME_TASK_UNCERTAIN');
|
|
51
|
+
if(start.input.phase==='validate_final'){
|
|
52
|
+
const observed=journal.rows.find(r=>r.type==='chrome_page_case_observed'&&r.owner_id===start.owner_id);
|
|
53
|
+
if(!observed)throw fail('A prior final case has no original settlement.','CHROME_TASK_UNCERTAIN');
|
|
54
|
+
readCase({phase:'validate_final',index:names.length,owner_id:start.owner_id,receipt_id:observed.receipt_id},journal);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const entries=records.map((record,index)=>{if(record.phase!=='run'||record.index!==index)throw fail('Skipped or duplicate case history.');return readCase(record,journal);});
|
|
58
|
+
for(let i=0;i<entries.length;i++){
|
|
59
|
+
const entry=entries[i],before=digest(basis(entry.input.context));
|
|
60
|
+
if(entry.input.previous!==null||before!==(i?digest(basis(entries[i-1].result.context)):value.case_series?.base_context_digest)||i<entries.length-1&&entry.result.status!=='verified')throw fail('Case context/lifetime chain or prior failure differs.');
|
|
61
|
+
}
|
|
62
|
+
if(entries.length&&!value.case_pending&&digest(basis(entries.at(-1).result.context))!==digest(basis(await context())))throw fail('Completed cases no longer belong to the current source/document.','CHROME_TASK_STALE_EVIDENCE');
|
|
63
|
+
return entries;
|
|
64
|
+
};
|
|
65
|
+
const ensureSeries=async options=>{
|
|
66
|
+
let value=await state();
|
|
67
|
+
if(!value.case_series){
|
|
68
|
+
if((value.case_progress||[]).length||value.case_pending)throw fail('Case progress has no original series owner.');
|
|
69
|
+
const started_at=new Date().toISOString(),base_context_digest=digest(basis(await context()));
|
|
70
|
+
value.case_series={started_at,base_context_digest,plan_digest:planDigest};
|
|
71
|
+
await options.recordWork({type:'chrome_page_case_series_started',source:'workflow_host',task_digest:taskDigest,attempt_id:value.attempt_id,...value.case_series});await save(options,value);
|
|
72
|
+
}
|
|
73
|
+
const journal=await book(),births=journal.rows.filter(r=>r.type==='chrome_page_case_series_started'&&r.task_digest===taskDigest&&r.attempt_id===value.attempt_id);
|
|
74
|
+
if(births.length!==1||digest(Object.fromEntries(['started_at','base_context_digest','plan_digest'].map(k=>[k,births[0][k]])))!==digest(value.case_series)||value.case_series.plan_digest!==planDigest)throw fail('Original case series/budget identity changed.');
|
|
75
|
+
const elapsed=Date.now()-Date.parse(value.case_series.started_at);
|
|
76
|
+
if(!Number.isFinite(elapsed)||elapsed<0||elapsed>=PAGE_CASE_LIMITS.timeout_ms)throw fail('Accepted case series wall budget is unavailable or exhausted.','CHROME_TASK_CASE_BUDGET');
|
|
77
|
+
const owners=new Set(journal.rows.filter(r=>r.type==='chrome_page_case_started'&&r.task_digest===taskDigest&&r.input.context.publication.attempt_id===value.attempt_id).map(r=>r.owner_id));
|
|
78
|
+
const phases=new Set(journal.rows.filter(r=>r.type==='house_proposal'&&r.operation===PAGE_BEHAVIOR_OPERATION&&owners.has(r.parent_invocation_id)).map(r=>r.invocation_id));
|
|
79
|
+
const used=journal.rows.filter(r=>r.type==='house_proposal'&&r.operation.startsWith('plugin__chrome-lab__')&&phases.has(r.parent_invocation_id)).length;
|
|
80
|
+
if(used>PAGE_CASE_LIMITS.max_behavior_calls-PAGE_VERIFIER_CONTRACT.max_calls)throw fail('Insufficient reserved native behavior-call budget for another bounded phase.','CHROME_TASK_CASE_BUDGET');
|
|
81
|
+
return {remaining:PAGE_CASE_LIMITS.timeout_ms-elapsed,used};
|
|
82
|
+
};
|
|
83
|
+
const summary=entries=>{
|
|
84
|
+
const result={status:'verified',plan_digest:planDigest,cases:entries.map(e=>({id:names[e.record.index],owner_id:e.record.owner_id,receipt_id:e.record.receipt_id,output_digest:digest(e.result),terminal_digest:digest(e.result.terminal)})),terminal:entries.at(-1).result.terminal};
|
|
85
|
+
if(Buffer.byteLength(JSON.stringify(result))>PAGE_CASE_LIMITS.max_summary_bytes)throw fail('Complete final case summary exceeds its accepted bound.');return result;
|
|
86
|
+
};
|
|
87
|
+
const settle=async(options,value)=>{
|
|
88
|
+
const checkpoint=pending(value);if(!checkpoint)throw fail('No pending case settlement exists.');
|
|
89
|
+
const journal=await book(),entry=readCase(checkpoint,journal),entries=await history(value,journal);
|
|
90
|
+
if(checkpoint.phase==='run'&&checkpoint.index!==entries.length||checkpoint.phase==='validate_final'&&entries.length!==names.length)throw fail('Pending case does not follow completed history.');
|
|
91
|
+
const expectedBefore=entries.length?digest(basis(entries.at(-1).result.context)):value.case_series?.base_context_digest;
|
|
92
|
+
if(digest(basis(entry.input.context))!==expectedBefore)throw fail('Pending case does not extend the original context chain.');
|
|
93
|
+
await fresh(options,value);
|
|
94
|
+
if(digest(entry.result.context)!==digest(await context()))throw fail('Case result is not the current source/document.','CHROME_TASK_STALE_EVIDENCE');
|
|
95
|
+
if(checkpoint.phase==='run'){
|
|
96
|
+
value.case_progress=[...(value.case_progress||[]),checkpoint];value.behavior_receipt_id=checkpoint.receipt_id;
|
|
97
|
+
value.status=entry.result.status==='needs_correction'?'needs_correction':'ready';value.verified_scope=null;value.final_validation_receipt_id=null;
|
|
98
|
+
}else{
|
|
99
|
+
if(digest(entry.input.previous)!==digest(summary(entries)))throw fail('Final validation did not bind all original case owners.');
|
|
100
|
+
value.status=entry.result.status;value.verified_scope=entry.result.status==='verified'?'declared_page_behavior':null;value.final_validation_receipt_id=entry.result.status==='verified'?checkpoint.receipt_id:null;value.case_final_owner=checkpoint.owner_id;
|
|
101
|
+
}
|
|
102
|
+
value.stage='checked';value.case_cursor=null;delete value.case_pending;await save(options,value);return entry;
|
|
103
|
+
};
|
|
104
|
+
const invoke=async(options,phase,index)=>{
|
|
105
|
+
await ensureSeries(options);const input={phase,index};frame={input,operation:caseOperation};
|
|
106
|
+
try{
|
|
107
|
+
const output=JSON.parse(await options.invoke(PAGE_CASE_OPERATION,input,{signal:options.signal}));
|
|
108
|
+
const value=await state();if(value.case_pending?.receipt_id!==output.native_receipt_id)throw fail('Case returned without its exact pending result.','CHROME_TASK_UNCERTAIN');
|
|
109
|
+
return await settle(options,value);
|
|
110
|
+
}finally{frame=null;}
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
bind(operation,nativeOperation){caseOperation=operation;behaviorOperation=nativeOperation;},
|
|
114
|
+
admit(view){return Boolean(frame&&getOperation(PAGE_CASE_OPERATION)===caseOperation&&view.parent_operation==='ChromeTest'&&view.contract_digest===frame.operation.digest&&digest(view.input)===digest(frame.input));},
|
|
115
|
+
async executeCase({phase,index},options){
|
|
116
|
+
if(!frame||digest(frame.input)!==digest({phase,index}))throw fail('Case phase was not admitted by its original coordinator.');
|
|
117
|
+
const value=await state(),entries=await history(value);await ensureSeries(options);
|
|
118
|
+
if(value.stage.endsWith('_pending')||value.status==='uncertain'||phase==='run'&&(index!==entries.length||entries.at(-1)?.result.status==='needs_correction'||index>=names.length)||phase==='validate_final'&&(index!==names.length||entries.length!==names.length||entries.some(e=>e.result.status!=='verified')))throw fail('Case order or execution state is unavailable.','CHROME_TASK_UNCERTAIN');
|
|
119
|
+
const previous=phase==='run'?null:summary(entries);
|
|
120
|
+
await fresh(options,value);value.stage='case_pending';value.case_cursor=meta(phase,index);value.case_pending={phase,index,owner_id:options.invocation_id};await save(options,value);
|
|
121
|
+
const input={phase,context:await context(),previous};
|
|
122
|
+
await options.recordWork({type:'chrome_page_case_started',source:'workflow_host',task_digest:taskDigest,owner_id:options.invocation_id,case:meta(phase,index),input});
|
|
123
|
+
const observed=await runBehavior(options,input);value.case_pending.receipt_id=observed.receipt_id;
|
|
124
|
+
// Store the returned native reference, but keep the phase pending until
|
|
125
|
+
// the separate Case owner receipt has actually returned and is checked.
|
|
126
|
+
const current=await state();current.case_pending=value.case_pending;await save(options,current);
|
|
127
|
+
await options.recordWork({type:'chrome_page_case_observed',source:'workflow_host',task_digest:taskDigest,owner_id:options.invocation_id,receipt_id:observed.receipt_id,output_digest:observed.output_digest});
|
|
128
|
+
return {status:observed.result.status,phase,case:meta(phase,index),native_receipt_id:observed.receipt_id};
|
|
129
|
+
},
|
|
130
|
+
async verify(options){
|
|
131
|
+
const parentSignal=options.signal,budget=await ensureSeries(options),deadline=AbortSignal.timeout(Math.max(1,budget.remaining)),signal=AbortSignal.any([...(parentSignal?[parentSignal]:[]),deadline]);options={...options,signal};
|
|
132
|
+
try{
|
|
133
|
+
let value=await state(),entries=await history(value);
|
|
134
|
+
while(entries.length<names.length&&entries.at(-1)?.result.status!=='needs_correction'){
|
|
135
|
+
await invoke(options,'run',entries.length);value=await state();entries=await history(value);
|
|
136
|
+
}
|
|
137
|
+
const failed=entries.find(e=>e.result.status!=='verified');
|
|
138
|
+
if(failed)return {status:'needs_correction',scope:'declared_page_behavior',case:meta('run',failed.record.index),behavior:{status:failed.result.status,facts:failed.result.facts},receipt_id:failed.record.receipt_id,next_action:'Correct application source and publish a new complete candidate. Completed case effects are not replayed.',asked:'unjudged'};
|
|
139
|
+
summary(entries); // Reject oversized final input before dispatch/checkpoint.
|
|
140
|
+
const final=await invoke(options,'validate_final',names.length);
|
|
141
|
+
return {status:final.result.status,scope:'declared_page_behavior',phase:'validate_final',completed_cases:[...names],receipt_id:final.record.receipt_id,behavior:{status:final.result.status,facts:final.result.facts},asked:'unjudged'};
|
|
142
|
+
}catch(error){
|
|
143
|
+
if(deadline.aborted&&error===deadline.reason&&!parentSignal?.aborted)throw fail('Accepted case series time budget exhausted; interrupted effects remain uncertain.','CHROME_TASK_CASE_BUDGET');
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
async recover(options,value){
|
|
148
|
+
if(value.stage!=='case_pending'||!value.case_pending?.receipt_id)throw fail('Original returned case/native owners are unavailable; uncertain effects cannot replay.','CHROME_TASK_UNCERTAIN');
|
|
149
|
+
await settle(options,value);return {status:'recovered',scope:'original_case_receipt_settlement_only',next_action:'ChromeTest',asked:'unjudged'};
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Identity of the actually loaded coordinator functions/constants, not merely a
|
|
155
|
+
// caller-selected contract revision or a later filesystem hash.
|
|
156
|
+
export const PAGE_CASE_IMPLEMENTATION_DIGEST=digest({operation:PAGE_CASE_OPERATION,input:PAGE_CASE_INPUT,limits:PAGE_CASE_LIMITS,verifier_contract:PAGE_VERIFIER_CONTRACT,native_failure_kinds:NATIVE_PAGE_FAILURE_KINDS,functions:[createPageCaseCoordinator,returned,basis,fail,nativePageFailure].map(fn=>Function.prototype.toString.call(fn))});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Internal host opt-in only. No model tool, task-file field or end-user flag.
|
|
2
|
+
import {createSessionProjectionReader} from '../session-projections.mjs';
|
|
3
|
+
import {SessionJournalCache} from '../session-read-cache.mjs';
|
|
4
|
+
import {auditJournal} from '../audit.mjs';
|
|
5
|
+
import {assertClassifierChannels} from '../event-channels.mjs';
|
|
6
|
+
export function selectChromePageCheckpointReader(store,selection){
|
|
7
|
+
if(selection===false||selection===undefined)return undefined;
|
|
8
|
+
if(selection===true)return createSessionProjectionReader({store,JournalCache:SessionJournalCache,auditJournal,assertClassifierChannels});
|
|
9
|
+
throw new TypeError('Internal checkpointProjection must be true, false or absent.');
|
|
10
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Historical publication facts only. No native calls, recovery admission or cause inference.
|
|
2
|
+
import { auditExecution } from '../audit.mjs';
|
|
3
|
+
import { digest } from '../operations.mjs';
|
|
4
|
+
|
|
5
|
+
const diagnoses = new WeakMap();
|
|
6
|
+
const prefix = 'plugin__chrome-lab__';
|
|
7
|
+
const same = (a, b) => digest(a) === digest(b);
|
|
8
|
+
const law = row => ({ plan_hash: row.governance_plan_hash, mechanism_stamp: row.mechanism_stamp, activation_id: row.governance_activation_id });
|
|
9
|
+
const text = (value, max) => typeof value === 'string' && value.length > 0 && value.length <= max;
|
|
10
|
+
const ref = row => ({ operation: row.operation, receipt_id: row.invocation_id });
|
|
11
|
+
|
|
12
|
+
// Refuse to project convenient row-shaped data from a different task, source,
|
|
13
|
+
// contract, actor or activation. An unavailable diagnosis never replaces the error.
|
|
14
|
+
export function pagePublicationDiagnostic({ book, house, cwd, sessionId, task, taskDigest, plan, sourceDigest, operations, activeOwner }) {
|
|
15
|
+
const rows = book.rows;
|
|
16
|
+
if (book.id !== sessionId || book.cwd !== cwd || !book.journalAudit.consistent) return;
|
|
17
|
+
const audit = auditExecution(book, house.stateContext());
|
|
18
|
+
if (audit.violations.length || audit.unresolved.length) return;
|
|
19
|
+
const accepted = rows.findLast(row => row.type === 'chrome_task_accepted' && row.source === 'workflow_host');
|
|
20
|
+
const checkpoint = rows.findLast(row => row.type === 'chrome_task_checkpoint' && row.task_digest === taskDigest);
|
|
21
|
+
const value = checkpoint?.state;
|
|
22
|
+
if (accepted?.task_digest !== taskDigest || !same(accepted.task, task) || accepted.governance_plan_hash !== plan.plan_hash ||
|
|
23
|
+
!['deploy_pending', 'page_open_pending', 'published', 'checked'].includes(value?.stage) || value.source_digest !== sourceDigest ||
|
|
24
|
+
checkpoint.source !== 'workflow_host' || checkpoint.house !== cwd || checkpoint.session_id !== sessionId) return;
|
|
25
|
+
const proposals = id => rows.filter(row => row.type === 'house_proposal' && row.invocation_id === id);
|
|
26
|
+
const receipts = id => rows.filter(row => row.type === 'house_receipt' && row.invocation_id === id);
|
|
27
|
+
const publicationCheckpoint = rows.findLast(row => row.type === 'chrome_task_checkpoint' && row.source === 'workflow_host' && row.task_digest === taskDigest && row.state?.attempt_id === value.attempt_id && row.state.source_digest === value.source_digest && proposals(row.invocation_id).some(p => p.operation === 'ChromePublish'));
|
|
28
|
+
const owners = proposals(publicationCheckpoint?.invocation_id), owner = owners[0], current = house.governance(sessionId);
|
|
29
|
+
// Forked effective-law publication is not inferred from a local-plan hash.
|
|
30
|
+
if (owners.length !== 1 || owner.operation !== 'ChromePublish' || owner.house !== cwd || owner.session_id !== sessionId ||
|
|
31
|
+
owner.arguments_digest !== digest({}) || current?.source_stamp || !current ||
|
|
32
|
+
!same(law(owner), { plan_hash: current.plan_hash, mechanism_stamp: current.mechanism_stamp, activation_id: current.activation_id }) ||
|
|
33
|
+
current.plan_hash !== plan.plan_hash || !same(checkpoint.actor, owner.actor)) return;
|
|
34
|
+
const done = receipts(owner.invocation_id);
|
|
35
|
+
if (owner.invocation_id !== activeOwner && (done.length !== 1 || !['failed', 'returned'].includes(done[0].status)) || done.length > 1) return;
|
|
36
|
+
const children = rows.filter(row => row.type === 'house_proposal' && row.parent_invocation_id === owner.invocation_id);
|
|
37
|
+
const allowed = ['Status', 'Deploy', 'Preview', 'Targets', 'Contexts', 'Deployment', 'Assert'].map(name => prefix + name);
|
|
38
|
+
if (children.some(row => !allowed.includes(row.operation))) return;
|
|
39
|
+
const original = name => {
|
|
40
|
+
const nameRows = children.filter(row => row.operation === prefix + name);
|
|
41
|
+
if (nameRows.length !== 1) return;
|
|
42
|
+
const p = nameRows[0], matches = receipts(p.invocation_id), r = matches[0], op = operations.get(name);
|
|
43
|
+
if (!op || house.registry.get(prefix + name) !== op || matches.length !== 1 || !['returned', 'failed', 'refused'].includes(r.status) ||
|
|
44
|
+
p.contract_digest !== op.digest || r.contract_digest !== op.digest || r.operation !== p.operation ||
|
|
45
|
+
p.house !== cwd || r.house !== cwd || p.session_id !== sessionId || r.session_id !== sessionId ||
|
|
46
|
+
r.parent_invocation_id !== owner.invocation_id || !same(p.actor, owner.actor) || !same(r.actor, owner.actor) ||
|
|
47
|
+
!same(law(p), law(owner)) || !same(law(r), law(owner)) || p.arguments_digest !== r.arguments_digest ||
|
|
48
|
+
rows.indexOf(p) <= rows.indexOf(owner) || rows.indexOf(r) <= rows.indexOf(p) ||
|
|
49
|
+
rows.some(row => row.type === 'house_proposal' && row.parent_invocation_id === p.invocation_id)) return;
|
|
50
|
+
if (r.status !== 'returned') return { proposal: p, receipt: r };
|
|
51
|
+
if (r.output_truncated || r.output_digest !== digest(r.output)) return;
|
|
52
|
+
try { return { proposal: p, receipt: r, output: JSON.parse(r.output) }; } catch { return; }
|
|
53
|
+
};
|
|
54
|
+
const deployment = original('Deploy');
|
|
55
|
+
const deployInput = { project: task.project, directory: task.directory, attempt_id: value.attempt_id, expected_manifest_sha256: value.file_manifest_sha256, ...(value.create_only ? { create_only: true } : {}) };
|
|
56
|
+
const d = deployment?.output;
|
|
57
|
+
if (!d || deployment.proposal.arguments_digest !== digest(deployInput) || !same(d, value.deployment) ||
|
|
58
|
+
d.project !== task.project || d.attempt_id !== value.attempt_id || d.file_manifest_sha256 !== value.file_manifest_sha256 ||
|
|
59
|
+
!text(d.extension_id, 240) || !text(value.attempt_id, 240)) return;
|
|
60
|
+
const result = {
|
|
61
|
+
format: 'comal-page-publication-diagnostic/1', scope: 'original_receipts_current_task_source_and_law',
|
|
62
|
+
stage: value.stage, publication_owner: owner.invocation_id, source_digest: value.source_digest,
|
|
63
|
+
deployment: { status: 'acknowledged', ...ref(deployment.receipt), attempt_id: value.attempt_id, extension_id: d.extension_id },
|
|
64
|
+
preview: { status: 'acknowledgement_unknown' }, document_identity: value.binding?.identity ? 'recorded_not_revalidated' : 'unobserved',
|
|
65
|
+
automatic_effect_replay: false, recovery_admission: 'unchanged', semantic_credit: false,
|
|
66
|
+
};
|
|
67
|
+
const preview = original('Preview'), opened = preview?.output;
|
|
68
|
+
if (preview && preview.proposal.arguments_digest === digest({ project: task.project, file: task.fixture.file })) {
|
|
69
|
+
result.preview = { status: 'acknowledgement_unknown', ...ref(preview.receipt) };
|
|
70
|
+
if (opened && text(opened.target_id, 240) && text(opened.url, 3000) && opened.lifetime === 'plugin_connection' &&
|
|
71
|
+
rows.indexOf(preview.proposal) > rows.indexOf(deployment.receipt) && value.binding?.target_id === opened.target_id && value.binding?.url === opened.url &&
|
|
72
|
+
value.binding.project === task.project && value.binding.fixture_source_digest === accepted.fixture_source_digest) {
|
|
73
|
+
const url = new URL(opened.url);
|
|
74
|
+
if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || decodeURIComponent(url.pathname) !== '/' + task.fixture.file) return;
|
|
75
|
+
result.preview = { status: 'acknowledged', ...ref(preview.receipt), target_id: opened.target_id, url: opened.url };
|
|
76
|
+
const read = original('Contexts');
|
|
77
|
+
if (!value.binding.identity && read && ['failed', 'refused'].includes(read.receipt.status) && read.proposal.arguments_digest === digest({ target_id: opened.target_id }) && rows.indexOf(read.proposal) > rows.indexOf(preview.receipt)) {
|
|
78
|
+
result.stage = 'awaiting_document_identity';
|
|
79
|
+
result.read_failure = { ...ref(read.receipt), status: read.receipt.status, cause: 'not_established' };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const starts = rows.filter(row => row.type === 'chrome_page_case_started' && row.source === 'workflow_host' && row.task_digest === taskDigest && row.house === cwd && row.session_id === sessionId && row.input?.context?.source_digest === value.source_digest && row.input.context.publication?.attempt_id === value.attempt_id && same(row.actor, owner.actor));
|
|
84
|
+
const ownersStarted = new Set(starts.map(row => row.owner_id));
|
|
85
|
+
const observations = rows.filter(row => row.type === 'chrome_page_case_observed' && row.source === 'workflow_host' && row.task_digest === taskDigest && row.house === cwd && row.session_id === sessionId && ownersStarted.has(row.owner_id) && same(row.actor, owner.actor));
|
|
86
|
+
result.case_activity = { scope: 'audited_original_host_records_not_acceptance', started_records: starts.length, observed_records: observations.length };
|
|
87
|
+
// The URL and fixed facts fit the ordinary bounded diagnostic surface. Never
|
|
88
|
+
// truncate IDs or synthesize an executed:false claim to make an envelope fit.
|
|
89
|
+
if (Buffer.byteLength(JSON.stringify(result), 'utf8') <= 4000) return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Trusted in-process workflow mark, not a plugin-provided details/code convention.
|
|
93
|
+
// The current original House receipt chain must still bind the model call.
|
|
94
|
+
export function markPagePublicationDiagnostic(error, facts, { invocation_id, sessionId, cwd }) {
|
|
95
|
+
diagnoses.set(error, { facts: structuredClone(facts), invocation_id, sessionId, cwd, code: error.code, message: error.message });
|
|
96
|
+
error.details = { ...error.details, page_publication: structuredClone(facts) };
|
|
97
|
+
}
|
|
98
|
+
export function pagePublicationDiagnosticForCall(error, { rows, sessionId, callId, operation }) {
|
|
99
|
+
const mark = diagnoses.get(error);
|
|
100
|
+
if (!mark || mark.sessionId !== sessionId || mark.code !== error.code || mark.message !== error.message) return;
|
|
101
|
+
const matches = rows.filter(row => row.type === 'house_receipt' && row.invocation_id === mark.invocation_id);
|
|
102
|
+
let leaf = matches[0];
|
|
103
|
+
if (matches.length !== 1 || !['ChromePublish', 'ChromeRecover'].includes(leaf.operation)) return;
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
while (leaf) {
|
|
106
|
+
if (seen.has(leaf.invocation_id) || leaf.house !== mark.cwd || leaf.session_id !== sessionId || leaf.status !== 'failed' || leaf.code !== mark.code || leaf.error !== mark.message) return;
|
|
107
|
+
seen.add(leaf.invocation_id);
|
|
108
|
+
if (leaf.call_id === callId && leaf.operation === operation) return structuredClone(mark.facts);
|
|
109
|
+
const parents = rows.filter(row => row.type === 'house_receipt' && row.invocation_id === leaf.parent_invocation_id);
|
|
110
|
+
if (parents.length !== 1 || parents[0].operation !== 'ChromeDeliver') return;
|
|
111
|
+
leaf = parents[0];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Bind scoped page receipts to their ORIGINAL effective activation before any
|
|
2
|
+
// child dispatch. Accepted task/corpus identity remains the local page plan.
|
|
3
|
+
import {digest} from '../operations.mjs';
|
|
4
|
+
|
|
5
|
+
const fail=message=>Object.assign(new Error(message),{code:'CHROME_TASK_UNCERTAIN'});
|
|
6
|
+
const identity=row=>({plan_hash:row.governance_plan_hash,mechanism_stamp:row.mechanism_stamp,activation_id:row.governance_activation_id});
|
|
7
|
+
function activation(book,law,{cwd,sessionId,plan}){
|
|
8
|
+
const matches=book.rows.filter(r=>r.type==='governance_activated'&&r.source==='governance_host'&&r.house===cwd&&r.session_id===sessionId&&r.plan?.plan_hash===law.plan_hash&&r.mechanism_stamp===law.mechanism_stamp&&r.activation_id===law.activation_id);
|
|
9
|
+
if(matches.length!==1)throw fail('Original page effective-law activation is unavailable.');
|
|
10
|
+
const effective=matches[0],refs=effective.inherited_from;
|
|
11
|
+
if(!Array.isArray(refs)||digest(refs)!==digest(effective.plan.inherited_from))throw fail('Page effective law has no original scoped lineage.');
|
|
12
|
+
const locals=refs.filter(r=>r.house===cwd&&r.session_id===sessionId&&r.plan_hash===plan.plan_hash);
|
|
13
|
+
if(locals.length!==1)throw fail('Effective law omits the accepted local page corpus.');
|
|
14
|
+
const local=book.rows.find(r=>r.type==='governance_activated'&&r.source==='governance_host'&&r.house===cwd&&r.session_id===sessionId&&r.activation_id===locals[0].activation_id&&r.mechanism_stamp===locals[0].mechanism_stamp&&digest(r.plan)===digest(plan));
|
|
15
|
+
if(!local||book.rows.indexOf(local)>=book.rows.indexOf(effective))throw fail('Original local page activation does not verify.');return effective;
|
|
16
|
+
}
|
|
17
|
+
export function pageReceiptLaw(book,receiptId,{cwd,sessionId,taskDigest,plan,scoped}){
|
|
18
|
+
const receipt=book.rows.find(r=>r.type==='house_receipt'&&r.invocation_id===receiptId);
|
|
19
|
+
const births=book.rows.filter(r=>r.type==='house_scope_bound'&&r.house===cwd&&r.session_id===sessionId);
|
|
20
|
+
if(!scoped&&!births.length)return plan.plan_hash;
|
|
21
|
+
if(!scoped||births.length!==1)throw fail('Original forked page scope birth is unavailable.');
|
|
22
|
+
let p=book.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===receiptId);const descendant=p;
|
|
23
|
+
for(let depth=0;p&&p.operation!=='ChromeTest'&&depth<4;depth++)p=book.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===p.parent_invocation_id);
|
|
24
|
+
if(!p||p.operation!=='ChromeTest'||!receipt)throw fail('Scoped page receipt has no original native test owner.');
|
|
25
|
+
const markers=book.rows.filter(r=>r.type==='chrome_page_effective_law_bound'&&r.invocation_id===p.invocation_id),m=markers[0];
|
|
26
|
+
if(markers.length!==1||m.source!=='workflow_host'||m.task_digest!==taskDigest||m.local_plan_hash!==plan.plan_hash||m.house!==cwd||m.session_id!==sessionId||digest(m.actor)!==digest(p.actor)||digest(m.effective)!==digest(identity(p))||digest(m.effective)!==digest(identity(receipt))||book.rows.indexOf(m)<=book.rows.indexOf(p)||book.rows.indexOf(m)>=book.rows.indexOf(descendant))throw fail('Scoped page receipt does not match its pre-dispatch effective-law reservation.');
|
|
27
|
+
if(!book.rows.some(r=>r.type==='house_scope_bound'&&r.scope_id===m.scope_id&&r.house===cwd&&r.session_id===sessionId&&book.rows.indexOf(r)<book.rows.indexOf(m)))throw fail('Page effective-law reservation belongs to another child.');
|
|
28
|
+
const effective=activation(book,m.effective,{cwd,sessionId,plan});if(book.rows.indexOf(effective)>=book.rows.indexOf(p))throw fail('Effective page law was attached after native dispatch.');return m.effective;
|
|
29
|
+
}
|
|
30
|
+
export function createPageLawBinding({house,store,sessionId,cwd,taskDigest,plan}){
|
|
31
|
+
return {
|
|
32
|
+
async bind(options){
|
|
33
|
+
const current=house.governance(sessionId);
|
|
34
|
+
// Preserve ordinary, unforked page behavior and its existing raw plan proof.
|
|
35
|
+
if(!current?.source_stamp)return;
|
|
36
|
+
await options.revalidate();const book=await store.load(sessionId),p=book.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===options.invocation_id),law={plan_hash:current.plan_hash,mechanism_stamp:current.mechanism_stamp,activation_id:current.activation_id};
|
|
37
|
+
const live=house.governance(sessionId),liveLaw=live&&{plan_hash:live.plan_hash,mechanism_stamp:live.mechanism_stamp,activation_id:live.activation_id};
|
|
38
|
+
if(!book.journalAudit.consistent||p?.operation!=='ChromeTest'||p.house!==cwd||p.session_id!==sessionId||digest(identity(p))!==digest(law)||digest(liveLaw)!==digest(law))throw fail('Current effective page law changed before native child reservation.');
|
|
39
|
+
const effective=activation(book,law,{cwd,sessionId,plan}),birth=book.rows.filter(r=>r.type==='house_scope_bound'&&r.house===cwd&&r.session_id===sessionId);
|
|
40
|
+
if(birth.length!==1||book.rows.indexOf(effective)>=book.rows.indexOf(p))throw fail('The effective page law is not active in its original child.');
|
|
41
|
+
const marker={type:'chrome_page_effective_law_bound',source:'workflow_host',task_digest:taskDigest,local_plan_hash:plan.plan_hash,scope_id:birth[0].scope_id,invocation_id:p.invocation_id,actor:p.actor,house:cwd,session_id:sessionId,effective:law};
|
|
42
|
+
if(book.rows.some(r=>r.type===marker.type&&r.invocation_id===p.invocation_id))throw fail('Page effective-law reservation is duplicated.');
|
|
43
|
+
await options.recordWork(marker);
|
|
44
|
+
},
|
|
45
|
+
receiptLaw:(book,receiptId)=>pageReceiptLaw(book,receiptId,{cwd,sessionId,taskDigest,plan,scoped:Boolean(house.governance(sessionId)?.source_stamp)}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export const PAGE_LAW_IMPLEMENTATION_DIGEST=digest([createPageLawBinding,pageReceiptLaw,activation,identity,fail].map(fn=>Function.prototype.toString.call(fn)));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Private normal-host binding. Never exposed as a model tool or raw client API.
|
|
2
|
+
import {digest} from '../operations.mjs';
|
|
3
|
+
import {createPreviewTransportGuardV13,PAGE_PREVIEW_RECOVERY_V13} from './chrome-page-recovery-v13.mjs';
|
|
4
|
+
const fail=()=>Object.assign(new Error('Explicit original v13 task and current activated native client binding required.'),{code:'CHROME_PUBLICATION_RECOVERY_REFUSED'});
|
|
5
|
+
export function selectChromePageRecoveryHostV13({explicitTask,saved,trusted=false}){
|
|
6
|
+
const task=explicitTask??(saved?.source==='workflow_host'&&saved.acceptance==='explicit_cli_file'?saved.task:undefined);
|
|
7
|
+
if(!task?.publication_recovery)return undefined;
|
|
8
|
+
if(!trusted||task.surface!=='page'||digest(task.publication_recovery)!==digest(PAGE_PREVIEW_RECOVERY_V13))throw fail();
|
|
9
|
+
let current;
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
bind(client){const guard=createPreviewTransportGuardV13(client);current?.retire();current=guard;return()=>{guard.retire();if(current===guard)current=undefined;};},
|
|
12
|
+
guard(){if(!current)throw fail();return current;},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Explicit prospective candidate. Not imported by the production v11/v12 route.
|
|
2
|
+
import {createHash,randomUUID} from 'node:crypto';
|
|
3
|
+
import {digest,immutable} from '../operations.mjs';
|
|
4
|
+
import {auditExecution} from '../audit.mjs';
|
|
5
|
+
import {ChromeLabClient} from '../plugins/chrome-lab/client.mjs';
|
|
6
|
+
const prefix='plugin__chrome-lab__',guards=new WeakSet(),retiredGuards=new WeakSet();
|
|
7
|
+
const limits=Object.freeze({version:13,max_reads:6,timeout_ms:10000,max_attempts:3,max_files:30});
|
|
8
|
+
const fail=(message,code='CHROME_PUBLICATION_RECOVERY_REFUSED')=>Object.assign(new Error(message),{code});
|
|
9
|
+
const exact=(a,b)=>digest(a)===digest(b),sha=v=>createHash('sha256').update(v).digest('hex');
|
|
10
|
+
const tuple=value=>({connection_id:value?.connection_id,browser_endpoint_id:value?.browser_endpoint_id});
|
|
11
|
+
const validTuple=value=>typeof value?.connection_id==='string'&&value.connection_id.length>0&&value.connection_id.length<=256&&typeof value?.browser_endpoint_id==='string'&&value.browser_endpoint_id.length>0&&value.browser_endpoint_id.length<=1500;
|
|
12
|
+
const fileList=snapshot=>snapshot.files.map(({path,sha256})=>({path,sha256}));
|
|
13
|
+
// No connect, reconnect, request, CDP, source read, or asynchronous fallback here.
|
|
14
|
+
export function createPreviewTransportGuardV13(client){
|
|
15
|
+
if(!(client instanceof ChromeLabClient))throw fail('An actual host-owned ChromeLabClient is required.');
|
|
16
|
+
const guard={
|
|
17
|
+
retire(){retiredGuards.add(guard);},
|
|
18
|
+
capture(expected){if(retiredGuards.has(guard)||!validTuple(expected)||!client.connection||!client.child||!client.info||client.closed||client.failure||client.pending.size||client.attaching.size||!exact(tuple(expected),{connection_id:client.connectionId,browser_endpoint_id:client.info.browser_endpoint_id}))throw fail('Original live publication transport unavailable.');return{tuple:tuple(expected),connection:client.connection,child:client.child,info:client.info};},
|
|
19
|
+
assert(lease){if(retiredGuards.has(guard)||!lease||client.closed||client.failure||client.reconnecting||client.connection!==lease.connection||client.child!==lease.child||client.info!==lease.info||client.child?.exitCode!=null||client.child?.signalCode!=null||!exact(lease.tuple,{connection_id:client.connectionId,browser_endpoint_id:client.info?.browser_endpoint_id}))throw fail('Original publication connection changed; recovery cannot reconnect.');},
|
|
20
|
+
identity(lease,target,identity){guard.assert(lease);const observed=client.resourceIdentity(target,{frameId:identity.frame_id,sessionId:client.sessions.get(target),connectionId:lease.tuple.connection_id});if(!exact(observed,identity))throw fail('The newly observed document is no longer current.');},
|
|
21
|
+
};
|
|
22
|
+
guards.add(guard);return Object.freeze(guard);
|
|
23
|
+
}
|
|
24
|
+
export function assertPageRecoveryV13Admission(task,transportGuard){if(!guards.has(transportGuard)||retiredGuards.has(transportGuard)||!exact(task?.publication_recovery,PAGE_PREVIEW_RECOVERY_V13))throw fail('Exact explicit v13 descriptor and original host transport guard required before verifier preparation.');}
|
|
25
|
+
export function createPagePreviewRecoveryV13({house,store,sessionId,cwd,task,taskDigest,plan,policy,transportGuard,snapshot,state,save,invoke}){
|
|
26
|
+
assertPageRecoveryV13Admission(task,transportGuard);
|
|
27
|
+
const operations=new Map(['Status','Deploy','Deployment','Preview','Targets','Contexts'].map(name=>[prefix+name,house.registry.get(prefix+name)]));
|
|
28
|
+
if([...operations.values()].some(op=>!op))throw fail('Original native publication/read operations are unavailable.');
|
|
29
|
+
const leases=new Map();let active=null;
|
|
30
|
+
const authority=()=>house.authority({policy,sessionId});
|
|
31
|
+
const epoch=book=>({reset:book.rows.findLast(row=>row.type==='reset')?.audit?.sha256??null,goal:book.goal?{id:book.goal.id,condition:book.goal.condition,status:book.goal.status}:null});
|
|
32
|
+
const checkOperations=()=>{for(const[name,op]of operations)if(house.registry.get(name)!==op)throw fail('An original native operation was replaced.');};
|
|
33
|
+
const readBook=async()=>{const book=await store.load(sessionId),audit=auditExecution(book,house.stateContext());if(book.id!==sessionId||book.cwd!==cwd||!book.journalAudit.consistent||audit.violations.length||audit.unresolved.length)throw fail('Original complete native book is unavailable.');return book;};
|
|
34
|
+
const receipt=(book,id,operation,owner,returned=true)=>{
|
|
35
|
+
const proposals=book.rows.filter(r=>r.type==='house_proposal'&&r.invocation_id===id),receipts=book.rows.filter(r=>r.type==='house_receipt'&&r.invocation_id===id),starts=book.rows.filter(r=>r.type==='house_started'&&r.invocation_id===id);
|
|
36
|
+
const p=proposals[0],r=receipts[0],op=operations.get(operation),law=house.governance(sessionId);
|
|
37
|
+
if(proposals.length!==1||receipts.length!==1||p.operation!==operation||r.operation!==operation||p.parent_invocation_id!==owner||r.parent_invocation_id!==owner||r.session_id!==sessionId||r.house!==cwd||!exact(p.actor,r.actor)||p.arguments_digest!==r.arguments_digest||p.contract_digest!==r.contract_digest||r.governance_plan_hash!==plan.plan_hash||p.governance_plan_hash!==plan.plan_hash||r.mechanism_stamp!==law?.mechanism_stamp||p.mechanism_stamp!==law?.mechanism_stamp||r.governance_activation_id!==law?.activation_id||p.governance_activation_id!==law?.activation_id||op&&r.contract_digest!==op.digest||returned&&(r.status!=='returned'||r.output_truncated||r.output_digest!==digest(r.output))||!returned&&!['failed','refused'].includes(r.status)||r.status!=='refused'&&starts.length!==1||book.rows.indexOf(p)>=book.rows.indexOf(r))throw fail('Original publication owner/child receipt is missing or incompatible.');
|
|
38
|
+
return{proposal:p,row:r,value:returned?JSON.parse(r.output):null};
|
|
39
|
+
};
|
|
40
|
+
const originalProof=(book,value,lease)=>{
|
|
41
|
+
const a=book.rows.findLast(r=>r.type==='chrome_task_accepted'&&r.source==='workflow_host');
|
|
42
|
+
if(a?.task_digest!==taskDigest||digest(a.task)!==taskDigest||a.governance_profile?.version!=='13'||a.governance_plan_hash!==plan.plan_hash||!exact(a.task.publication_recovery,PAGE_PREVIEW_RECOVERY_V13))throw fail('Only the original prospectively accepted v13 task can recover.');
|
|
43
|
+
const obs=value.publication_observation;
|
|
44
|
+
if(value.stage!=='page_open_pending'||value.status!=='ready'||!obs||!lease||obs.lease_id!==lease.id||obs.owner_id!==lease.owner||!exact(obs.transport,lease.transport.tuple)||!exact(epoch(book),lease.epoch)||!exact(value,lease.checkpoint))throw fail('Original publication checkpoint/lease changed.');
|
|
45
|
+
const own=book.rows.filter(r=>r.type==='house_receipt'&&r.invocation_id===obs.owner_id),owner=own[0],proposal=book.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===obs.owner_id);
|
|
46
|
+
if(own.length!==1||owner.operation!=='ChromePublish'||owner.status!=='failed'||proposal?.arguments_digest!==digest({})||owner.session_id!==sessionId||owner.house!==cwd||!exact(owner.actor,lease.actor)||owner.governance_plan_hash!==plan.plan_hash||owner.mechanism_stamp!==lease.law.mechanism_stamp||owner.governance_activation_id!==lease.law.activation_id||owner.contract_digest!==lease.publishContract||proposal.contract_digest!==owner.contract_digest)throw fail('Original failed ChromePublish owner did not settle.');
|
|
47
|
+
if(proposal.parent_invocation_id){const parent=book.rows.find(r=>r.type==='house_receipt'&&r.invocation_id===proposal.parent_invocation_id);const parentProposal=book.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===proposal.parent_invocation_id);if(parent?.operation!=='ChromeDeliver'||parent.status!=='failed'||parent.parent_invocation_id!==null||parent.session_id!==sessionId||parent.house!==cwd||!exact(parent.actor,lease.actor)||parentProposal?.arguments_digest!==digest({})||book.rows.filter(r=>r.type==='house_proposal'&&r.parent_invocation_id===parent.invocation_id).some(r=>r.invocation_id!==owner.invocation_id))throw fail('Original candidate parent is missing or contains other effects.');}
|
|
48
|
+
const children=book.rows.filter(r=>r.type==='house_proposal'&&r.parent_invocation_id===obs.owner_id),expected=['Status','Deploy','Preview','Status','Targets','Contexts'];
|
|
49
|
+
if(children.length!==expected.length||children.some((r,i)=>r.operation!==prefix+expected[i]||!exact(r.actor,lease.actor)||r.session_id!==sessionId||r.house!==cwd)||book.rows.some(r=>r.type==='house_proposal'&&children.some(child=>child.invocation_id===r.parent_invocation_id)))throw fail('Original descendants are not the exact acknowledged-Preview/read-failure sequence.');
|
|
50
|
+
const parsed=children.map((p,i)=>receipt(book,p.invocation_id,p.operation,obs.owner_id,i<5));
|
|
51
|
+
if(parsed[0].row.invocation_id!==obs.status_before_id||parsed[1].row.invocation_id!==obs.deploy_id||parsed[2].row.invocation_id!==obs.preview_id||parsed[3].row.invocation_id!==obs.status_after_id||!exact(tuple(parsed[0].value),obs.transport)||!exact(tuple(parsed[3].value),obs.transport)||parsed[0].proposal.arguments_digest!==digest({})||parsed[3].proposal.arguments_digest!==digest({}))throw fail('Original transport observations do not bind Preview.');
|
|
52
|
+
if(parsed[1].proposal.arguments_digest!==digest({project:task.project,directory:task.directory,attempt_id:value.attempt_id,expected_manifest_sha256:value.file_manifest_sha256,...(value.create_only?{create_only:true}:{})})||parsed[2].proposal.arguments_digest!==digest({project:task.project,file:task.fixture.file})||!exact(parsed[1].value,value.deployment)||parsed[2].value.target_id!==value.binding?.target_id||parsed[2].value.url!==value.binding?.url||parsed[2].value.lifetime!=='plugin_connection'||value.binding.identity||value.binding.project!==task.project)throw fail('Original deployment/Preview acknowledgment differs from its checkpoint.');
|
|
53
|
+
if(parsed[4].proposal.arguments_digest!==digest({})||parsed[5].proposal.arguments_digest!==digest({target_id:value.binding.target_id})||parsed[4].value.truncated||parsed[4].value.targets.filter(r=>r.target_id===value.binding.target_id&&r.type==='page'&&r.url===value.binding.url).length!==1)throw fail('Original exact target/read suffix is unavailable.');
|
|
54
|
+
const latest=book.rows.findLast(r=>r.type==='chrome_task_checkpoint'&&r.task_digest===taskDigest);
|
|
55
|
+
if(!latest||latest.invocation_id!==obs.owner_id||!exact(latest.state,value))throw fail('A different checkpoint superseded the original publication.');
|
|
56
|
+
const late=book.rows.slice(book.rows.indexOf(owner)+1).filter(r=>r.type==='house_proposal');
|
|
57
|
+
for(const r of late){
|
|
58
|
+
if(r.operation==='ChromeRecover'&&r.parent_invocation_id===null&&r.arguments_digest===digest({}))continue;
|
|
59
|
+
if(['Deployment','Targets','Contexts'].map(n=>prefix+n).includes(r.operation)&&book.rows.some(p=>p.type==='house_proposal'&&p.invocation_id===r.parent_invocation_id&&p.operation==='ChromeRecover'))continue;
|
|
60
|
+
const done=book.rows.find(p=>p.type==='house_receipt'&&p.invocation_id===r.invocation_id),contract=book.rows.findLast(p=>p.type==='house_contract'&&p.contract_digest===r.contract_digest)?.contract;
|
|
61
|
+
if(['Read','FileInfo','SourceRead'].includes(r.operation)&&done?.status==='returned'&&contract?.effects?.external==='none'&&contract.effects.process==='none'&&contract.effects.authority==='none')continue;
|
|
62
|
+
if(['ChromeInspect','ChromeVerify','ChromeTest','ChromePublish','ChromeDeliver'].includes(r.operation)&&['failed','refused'].includes(done?.status)&&done.code==='CHROME_TASK_UNCERTAIN'&&!book.rows.some(p=>p.type==='house_proposal'&&p.parent_invocation_id===r.invocation_id))continue;
|
|
63
|
+
throw fail('Later application effects or another unknown owner prevent this recovery.');
|
|
64
|
+
}
|
|
65
|
+
return{owner_id:owner.invocation_id,preview_id:parsed[2].row.invocation_id,failed_identity_id:parsed[5].row.invocation_id,checkpoint_sha256:latest.audit.sha256};
|
|
66
|
+
};
|
|
67
|
+
const stable=(lease,options)=>{options.signal?.throwIfAborted();checkOperations();for(const[name,op]of lease?.ownOperations||[])if(house.registry.get(name)!==op)throw fail('Original page kernel operation was replaced.');transportGuard.assert(lease.transport);if(authority()!==lease.authority)throw fail('Original/current publication authority changed.');};
|
|
68
|
+
const local=async(lease,options,value)=>{stable(lease,options);const current=await snapshot(options.signal);stable(lease,options);if(digest(fileList(current))!==value.source_digest||sha(JSON.stringify(fileList(current)))!==value.file_manifest_sha256||!exact(fileList(current),value.deployment.files))throw fail('Exact accepted local publication bytes changed.');return current;};
|
|
69
|
+
const physical=(remote,value,lease)=>{if(remote.status!=='observed'||remote.project!==task.project||remote.attempt_id!==value.attempt_id||remote.extension_id!==value.deployment.extension_id||remote.remote_path!==value.deployment.remote_path||remote.file_manifest_sha256!==value.file_manifest_sha256||remote.files_truncated||remote.file_count!==value.deployment.file_count||remote.file_count>limits.max_files||!exact(remote.files,value.deployment.files)||!exact(tuple(remote),lease.transport.tuple))throw fail('Exact physical publication or transport changed.');};
|
|
70
|
+
return{
|
|
71
|
+
async assertReturnedRecovery(value,book){
|
|
72
|
+
if(!value.recovery)return;
|
|
73
|
+
const r=value.recovery,owner=book.rows.find(row=>row.type==='house_receipt'&&row.invocation_id===r.owner_id),proposal=book.rows.find(row=>row.type==='house_proposal'&&row.invocation_id===r.owner_id);
|
|
74
|
+
if(r.format!=='comal-page-publication-recovery/13'||owner?.operation!=='ChromeRecover'||owner.status!=='returned'||owner.output_truncated||owner.output_digest!==digest(owner.output)||owner.session_id!==sessionId||owner.house!==cwd||proposal?.parent_invocation_id!==null||proposal.arguments_digest!==digest({}))throw fail('Recovery owner did not return; its observation cannot advance the page task.');
|
|
75
|
+
const result=JSON.parse(owner.output),children=book.rows.filter(row=>row.type==='house_proposal'&&row.parent_invocation_id===owner.invocation_id),names=['Deployment','Targets','Contexts','Deployment','Targets','Contexts'];
|
|
76
|
+
if(result.status!=='publication_ready_unverified'||result.attempt_id!==value.attempt_id||result.source_digest!==value.source_digest||result.observation_digest!==r.observation_digest||result.reads!==limits.max_reads||children.length!==names.length)throw fail('Original returned recovery outcome is incomplete.');
|
|
77
|
+
for(const[p,index]of children.map((p,i)=>[p,i])){const name=names[index],expected=name==='Deployment'?{project:task.project,reconnect:false}:name==='Targets'?{}:{target_id:value.binding.target_id};if(p.operation!==prefix+name||p.arguments_digest!==digest(expected))throw fail('Recovery owner has another child.');receipt(book,p.invocation_id,p.operation,owner.invocation_id);if(book.rows.some(row=>row.type==='house_proposal'&&row.parent_invocation_id===p.invocation_id))throw fail('Recovery read acquired an unexpected descendant.');}
|
|
78
|
+
},
|
|
79
|
+
async capture(options,namespace){const candidate=await snapshot(options.signal);if(candidate.files.length>limits.max_files)throw fail('Complete v13 publication evidence exceeds the explicit file bound.');checkOperations();const transport=transportGuard.capture(namespace),book=await readBook(),id=randomUUID(),lease={id,owner:options.invocation_id,actor:structuredClone(options.actor),authority:authority(),transport,epoch:epoch(book),publishContract:house.registry.get('ChromePublish').digest,law:house.governance(sessionId),ownOperations:new Map(['ChromePublish','ChromeRecover','ChromeDeliver','ChromeTest','ChromeFinish','ChromeVerify','ChromeInspect','ChromePageCase','ChromePageBehavior'].map(name=>[name,house.registry.get(name)]))};leases.set(id,lease);return id;},
|
|
80
|
+
async acknowledge(options,value,opened,namespace,id){const lease=leases.get(id);stable(lease,options);const observed=await invoke(options,'Status');stable(lease,options);if(!exact(tuple(namespace),tuple(observed)))throw fail('Preview transport changed before its acknowledgment.');const book=await readBook(),children=book.rows.filter(r=>r.type==='house_receipt'&&r.parent_invocation_id===options.invocation_id&&r.status==='returned'),statuses=children.filter(r=>r.operation===prefix+'Status'),deployment=children.find(r=>r.operation===prefix+'Deploy'),preview=children.find(r=>r.operation===prefix+'Preview');if(statuses.length!==2||!deployment||!preview||deployment.output_truncated||preview.output_truncated||!exact(JSON.parse(preview.output),opened)||!validTuple(observed)||value.deployment.files_truncated||value.deployment.file_count>limits.max_files||value.deployment.files.length!==value.deployment.file_count)throw fail('Complete prospective Preview/source evidence unavailable.');value.publication_observation={format:'comal-page-preview-observation/13',owner_id:options.invocation_id,lease_id:id,transport:tuple(observed),status_before_id:statuses[0].invocation_id,deploy_id:deployment.invocation_id,preview_id:preview.invocation_id,status_after_id:statuses[1].invocation_id};lease.checkpoint=structuredClone(value);await options.recordWork({type:'chrome_page_preview_acknowledged',source:'workflow_host',task_digest:taskDigest,...value.publication_observation,authority_stamp:lease.authority,epoch:lease.epoch});},
|
|
81
|
+
admit(view){if(!active||view.parent_invocation_id!==active.owner||!['Deployment','Targets','Contexts'].map(n=>prefix+n).includes(view.operation))return false;const name=view.operation.slice(prefix.length),input=name==='Deployment'?{project:task.project,reconnect:false}:name==='Targets'?{}:{target_id:active.target};return exact(view.input,input);},
|
|
82
|
+
async recover(options,value){
|
|
83
|
+
const lease=leases.get(value.publication_observation?.lease_id);if(!lease)throw fail('Original live publication lease is unavailable; no reconnect is authorized.');stable(lease,options);await local(lease,options,value);const initial=await readBook(),proof=originalProof(initial,value,lease),root=initial.rows.find(r=>r.type==='house_proposal'&&r.invocation_id===options.invocation_id);if(root?.parent_invocation_id!==null||!exact(root.actor,lease.actor))throw fail('Recovery must retain its original actor and root ownership.');
|
|
84
|
+
if(initial.rows.filter(r=>r.type==='chrome_page_publication_recovery_started'&&r.task_digest===taskDigest&&r.publication_owner===proof.owner_id).length>=limits.max_attempts)throw fail('Original publication recovery attempt budget exhausted.');
|
|
85
|
+
const signal=AbortSignal.any([...(options.signal?[options.signal]:[]),AbortSignal.timeout(limits.timeout_ms)]),bounded={...options,signal};let reads=0;active={owner:options.invocation_id,target:value.binding.target_id};
|
|
86
|
+
try{await options.recordWork({type:'chrome_page_publication_recovery_started',source:'workflow_host',task_digest:taskDigest,publication_owner:proof.owner_id,original_proof:proof,limits});stable(lease,bounded);
|
|
87
|
+
const read=async(name,input={})=>{if(++reads>limits.max_reads)throw fail('Recovery read bound exhausted.');stable(lease,bounded);await options.revalidate();stable(lease,bounded);const result=await invoke(bounded,name,input);stable(lease,bounded);await local(lease,bounded,value);const book=await readBook();originalProof(book,value,lease);return result;};
|
|
88
|
+
let identity;
|
|
89
|
+
for(let pass=0;pass<2;pass++){
|
|
90
|
+
physical(await read('Deployment',{project:task.project,reconnect:false}),value,lease);
|
|
91
|
+
const targets=await read('Targets');if(targets.truncated||!Array.isArray(targets.targets)||targets.targets.filter(t=>t.target_id===value.binding.target_id&&t.type==='page'&&t.url===value.binding.url).length!==1||targets.targets.some(t=>t.target_id!==value.binding.target_id&&t.type==='page'&&t.url===value.binding.url))throw fail('Exact original target is missing, duplicated or changed.');
|
|
92
|
+
const contexts=await read('Contexts',{target_id:value.binding.target_id}),next=contexts.identity;
|
|
93
|
+
if(!next?.context_unique_id||!next.frame_id||!exact(tuple(next),lease.transport.tuple)||identity&&!exact(next,identity))throw fail('A stable exact original document identity was not obtained.');identity=next;transportGuard.identity(lease.transport,value.binding.target_id,identity);
|
|
94
|
+
}
|
|
95
|
+
await local(lease,bounded,value);const finalBook=await readBook();originalProof(finalBook,value,lease);stable(lease,bounded);transportGuard.identity(lease.transport,value.binding.target_id,identity);await options.revalidate();stable(lease,bounded);
|
|
96
|
+
const observation_digest=digest({identity,proof,reads,attempt_id:value.attempt_id,source_digest:value.source_digest});
|
|
97
|
+
const current={...value,binding:{...value.binding,identity},stage:'published',status:'ready',recovery:{format:'comal-page-publication-recovery/13',owner_id:options.invocation_id,proof,reads,observation_digest,claim:'publication_ready_unverified'}};
|
|
98
|
+
if(['behavior_receipt_id','behavior_owner','test_owner','final_validation_receipt_id','verified_scope','case_series','case_progress','case_pending','case_cursor','case_final_owner'].some(k=>Object.hasOwn(value,k)))throw fail('Recovery cannot inherit semantic or Case credit.');
|
|
99
|
+
await save(options,current);stable(lease,bounded);return{status:'publication_ready_unverified',next_action:'ChromeTest',claim:'metadata_only_no_semantic_credit',attempt_id:value.attempt_id,source_digest:value.source_digest,original_publication_owner:proof.owner_id,original_preview:proof.preview_id,observation_digest,reads,asked:'unjudged'};
|
|
100
|
+
}finally{active=null;}
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export const PAGE_PREVIEW_RECOVERY_V13=immutable({format:'comal-page-preview-recovery/13',...limits,implementation_digest:digest({limits,functions:[createPreviewTransportGuardV13,createPagePreviewRecoveryV13,assertPageRecoveryV13Admission,tuple,validTuple,fileList,fail,exact,sha].map(fn=>Function.prototype.toString.call(fn))})});
|