@milpa/comal 0.1.7 → 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.
- 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 +40 -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 +73 -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 +61 -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,429 @@
|
|
|
1
|
+
// Existing-page coordinator. Historical popup workflows keep their original code/law.
|
|
2
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
6
|
+
import { compileGovernance } from '../governance.mjs';
|
|
7
|
+
import { digest } from '../operations.mjs';
|
|
8
|
+
import { registerWorkOperations, workProjection } from '../work-plan.mjs';
|
|
9
|
+
import { validateTaskDirectory, chromeFixtureDigest } from './chrome-task.mjs';
|
|
10
|
+
import { verifySuppliedInputs, isSuppliedPath, assertSuppliedSnapshot } from './chrome-supplied-inputs.mjs';
|
|
11
|
+
import { snapshotProject } from '../plugins/chrome-lab/project.mjs';
|
|
12
|
+
import { CHROME_SOURCE_NAMES, bindChromeSource } from './chrome-source.mjs';
|
|
13
|
+
import { PAGE_BEHAVIOR_OPERATION, PAGE_VERIFIER_CONTRACT, assertPageVerifier, pageBehaviorDefinition } from './chrome-page-verifier.mjs';
|
|
14
|
+
import { PAGE_CASE_OPERATION, PAGE_CASE_INPUT, PAGE_CASE_IMPLEMENTATION_DIGEST, createPageCaseCoordinator } from './chrome-page-cases.mjs';
|
|
15
|
+
import { createPageLawBinding } from './chrome-page-law.mjs';
|
|
16
|
+
import {createPagePreviewRecoveryV13,assertPageRecoveryV13Admission} from './chrome-page-recovery-v13.mjs';
|
|
17
|
+
import {selectChromePageCheckpointReader} from './chrome-page-checkpoint-reader.mjs';
|
|
18
|
+
import { nativePageFailure, nativeClosureRefusal } from '../native-refusal.mjs';
|
|
19
|
+
import { pagePublicationDiagnostic, markPagePublicationDiagnostic } from './chrome-page-diagnostics.mjs';
|
|
20
|
+
|
|
21
|
+
const prefix = 'plugin__chrome-lab__', own = ['ChromePublish', 'ChromeVerify', 'ChromeTest', 'ChromeFinish', 'ChromeDeliver', 'ChromeRecover', 'ChromeInspect'];
|
|
22
|
+
const publicNames = new Set(['Read', 'Write', 'Edit', ...own, ...CHROME_SOURCE_NAMES]);
|
|
23
|
+
const empty = { type: 'object', properties: {}, additionalProperties: false };
|
|
24
|
+
const error = (message, code = 'CHROME_TASK_CONFLICT') => nativePageFailure(message,code);
|
|
25
|
+
const sourceHash = value => digest(value.files.map(({ path, sha256 }) => ({ path, sha256 })));
|
|
26
|
+
const physicalHash = value => createHash('sha256').update(JSON.stringify(value.files.map(({ path, sha256 }) => ({ path, sha256 })))).digest('hex');
|
|
27
|
+
const accepted = book => book.rows.findLast(row => row.type === 'chrome_task_accepted' && row.source === 'workflow_host');
|
|
28
|
+
// Only host-owned, bounded diagnoses cross the close mechanism boundary. An
|
|
29
|
+
// arbitrary plugin/verifier exception remains subject to House's fail-closed
|
|
30
|
+
// generic error; neither its message nor its stack is projected here.
|
|
31
|
+
const closeRefusals = Object.freeze({
|
|
32
|
+
CHROME_TASK_CASE_BUDGET: 'A required original case-series time or native-call budget is unavailable or exhausted; no additional phase or Work closure is authorized.',
|
|
33
|
+
CHROME_TASK_UNCERTAIN: 'Original page effects or case settlement are uncertain; they cannot replay to close Work.',
|
|
34
|
+
CHROME_TASK_STALE_EVIDENCE: 'Original verification evidence no longer matches the current page or source.',
|
|
35
|
+
CHROME_TASK_STALE_SOURCE: 'Published source no longer matches the current source.',
|
|
36
|
+
CHROME_TASK_TARGET_CHANGED: 'The exact accepted page target is missing or changed.',
|
|
37
|
+
CHROME_TASK_DOCUMENT_CHANGED: 'The exact accepted page document changed.',
|
|
38
|
+
CHROME_TASK_FIXTURE_CHANGED: 'The immutable accepted page fixture changed.',
|
|
39
|
+
CHROME_VERIFIER_CHANGED: 'The original verifier or its bounded native port contract is unavailable or changed.',
|
|
40
|
+
HOUSE_REFUSED: 'An original native operation was denied; earlier successful reads do not authorize closure.',
|
|
41
|
+
HOUSE_GUARD: 'An original native guard denied a current closure check.',
|
|
42
|
+
HOUSE_AUTHORITY_CHANGED: 'Original authority changed before the current closure check.',
|
|
43
|
+
HOUSE_SURFACE_CHANGED: 'The original native tool surface changed before closure.',
|
|
44
|
+
HOUSE_STALE: 'The original native operation contract changed before closure.',
|
|
45
|
+
AUDIT_INVALID: 'The original native journal does not verify.',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export async function prepareChromePageTask({ cwd, house, store, sessionId, executor, policy, task: brief, verifier, source = 'explicit_cli_file', provenance, caseRevision, caseImplementation, publicationRecoveryGuard, checkpointProjection }) {
|
|
49
|
+
const recoverySelected=Object.hasOwn(brief,'publication_recovery');
|
|
50
|
+
if(recoverySelected)assertPageRecoveryV13Admission(brief,publicationRecoveryGuard);
|
|
51
|
+
if (caseRevision !== undefined && (!/^[a-f0-9]{64}$/.test(caseRevision) || caseImplementation !== PAGE_CASE_IMPLEMENTATION_DIGEST)) throw error('Host-reserved page Case implementation changed.', 'CHROME_VERIFIER_CHANGED');
|
|
52
|
+
const taskDigest = digest(brief), directory = await validateTaskDirectory(cwd, brief.directory);
|
|
53
|
+
assertPageVerifier(verifier, brief.verification); await verifier.assertCurrent(directory);
|
|
54
|
+
const fixtureDigest = await chromeFixtureDigest(cwd, brief), suppliedDigest = digest(brief.supplied_inputs);
|
|
55
|
+
await verifySuppliedInputs(directory, brief.supplied_inputs);
|
|
56
|
+
const loaded = await store.load(sessionId), saved = accepted(loaded);
|
|
57
|
+
if (!loaded.journalAudit.consistent) throw error('Invalid native journal.', 'AUDIT_INVALID');
|
|
58
|
+
if (saved && (saved.task_digest !== taskDigest || saved.fixture_source_digest !== fixtureDigest || saved.supplied_inputs_digest !== suppliedDigest)) throw error('Accepted page task or input identity changed.');
|
|
59
|
+
if (provenance?.fixture_source_digest && provenance.fixture_source_digest !== fixtureDigest || provenance?.supplied_inputs_digest && provenance.supplied_inputs_digest !== suppliedDigest) throw error('Page input provenance changed.');
|
|
60
|
+
const mechanisms = ['runtime:chrome-task.scope', 'runtime:chrome-task.close', 'runtime:chrome-task.artifact-hash'], casePlan = brief.verification.cases;
|
|
61
|
+
const plan = compileGovernance(JSON.parse(await readFile(new URL(`../../governance/chrome-task-v${recoverySelected ? 13 : casePlan ? 12 : 11}.json`, import.meta.url))), mechanisms);
|
|
62
|
+
if (saved && saved.governance_plan_hash !== plan.plan_hash) throw error('Page task requires its original accepted policy.');
|
|
63
|
+
const pageLaw = createPageLawBinding({ house, store, sessionId, cwd, taskDigest, plan });
|
|
64
|
+
await verifier.prepare(directory, brief.verification);
|
|
65
|
+
for (const name of ['Status', 'Deploy', 'Deployment', 'Preview', 'Targets', 'Contexts', 'Assert', 'ReadDOM', 'Console', 'InspectValue']) if (!house.registry.get(prefix + name)) throw error('Required native Chrome operation unavailable: ' + name);
|
|
66
|
+
const hashOp = house.registry.get('FileInfo');
|
|
67
|
+
if (!hashOp || hashOp.contract.origin !== null || hashOp.contract.steps || hashOp.contract.permission !== 'FileInfo' || hashOp.contract.effects.workspace !== 'read' || ['external', 'process', 'authority'].some(key => hashOp.contract.effects[key] !== 'none')) throw error('Native artifact hash capability unavailable.');
|
|
68
|
+
const artifact = brief.directory + '/manifest.json';
|
|
69
|
+
const declaredTask = { id: 'chrome-task', title: 'Implement and verify accepted existing-page extension', criterion: `Accepted page task ${taskDigest}; native verifier ${brief.verification.implementation_sha256}; see chrome_task_accepted.`, phase: 'implementation', artifacts: [artifact], checks: [{ step_id: 'browser', operation: 'ChromeTest', arguments: {}, assertions: [{ kind: 'json_equals', pointer: '/status', value: 'verified' }] }] };
|
|
70
|
+
const work = workProjection(loaded);
|
|
71
|
+
if (!saved && work) throw error('Use a fresh session to accept a page task.');
|
|
72
|
+
if (work) {
|
|
73
|
+
const definitions = loaded.rows[work.plan.ref]?.tasks;
|
|
74
|
+
const definition = definitions?.length === 1 ? Object.fromEntries(Object.entries(definitions[0]).filter(([key]) => !['definition_digest', 'work_digest'].includes(key))) : null;
|
|
75
|
+
if (work.plan.version !== 1 || digest(definition) !== digest({ ...declaredTask, depends_on: [] })) throw error('Saved work does not match the page task.');
|
|
76
|
+
}
|
|
77
|
+
const selectedStateReader=selectChromePageCheckpointReader(store,checkpointProjection);
|
|
78
|
+
// v13 returned recovery needs the original complete book, never an index tuple.
|
|
79
|
+
const pageStateReader=recoverySelected?undefined:selectedStateReader;
|
|
80
|
+
let publicationRecovery;
|
|
81
|
+
const state = async () => {
|
|
82
|
+
if(pageStateReader){
|
|
83
|
+
const projection=await pageStateReader.readPageState(sessionId,taskDigest);
|
|
84
|
+
if(!projection.journalAudit.consistent||projection.accepted?.task_digest!==taskDigest)throw error('Accepted page task journal changed.','AUDIT_INVALID');
|
|
85
|
+
return projection.state;
|
|
86
|
+
}
|
|
87
|
+
const book = await store.load(sessionId);
|
|
88
|
+
if (!book.journalAudit.consistent || accepted(book)?.task_digest !== taskDigest) throw error('Accepted page task journal changed.', 'AUDIT_INVALID');
|
|
89
|
+
const current=structuredClone(book.rows.findLast(row => row.type === 'chrome_task_checkpoint' && row.task_digest === taskDigest)?.state || { stage: 'new', status: 'ready' });
|
|
90
|
+
if(publicationRecovery)await publicationRecovery.assertReturnedRecovery(current,book);
|
|
91
|
+
return current;
|
|
92
|
+
};
|
|
93
|
+
const save = (options, value) => options.recordWork({ type: 'chrome_task_checkpoint', source: 'workflow_host', task_digest: taskDigest, state: value });
|
|
94
|
+
const snapshot = async signal => {
|
|
95
|
+
assertPageVerifier(verifier, brief.verification); await verifier.assertCurrent(directory); await verifySuppliedInputs(directory, brief.supplied_inputs, { signal });
|
|
96
|
+
const result = await snapshotProject(cwd, brief.directory, { signal }); assertSuppliedSnapshot(result, brief.supplied_inputs);
|
|
97
|
+
if (result.files.find(file => file.path === brief.fixture.file)?.sha256 !== fixtureDigest) throw error('Immutable page fixture changed.', 'CHROME_TASK_FIXTURE_CHANGED');
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
const assertReady = value => {
|
|
101
|
+
if (value.stage.endsWith('_pending') || value.status === 'uncertain') {
|
|
102
|
+
const failure = error('An interrupted page effect cannot be replayed.', 'CHROME_TASK_UNCERTAIN');
|
|
103
|
+
if (casePlan && value.stage === 'case_pending') failure.details = { recovery_call: { name: 'ChromeRecover', input: {} }, next_action: 'Read-only reconciliation can settle only an original returned Case owner and native receipt. Missing owners or uncertain effects stay blocked; do not repeat the case.' };
|
|
104
|
+
throw failure;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const invoke = async (options, name, input = {}) => JSON.parse(await options.invoke(prefix + name, input, { signal: options.signal }));
|
|
108
|
+
if(recoverySelected)publicationRecovery=createPagePreviewRecoveryV13({house,store,sessionId,cwd,task:brief,taskDigest,plan,policy,transportGuard:publicationRecoveryGuard,snapshot,state,save,invoke});
|
|
109
|
+
let behaviorOperation;
|
|
110
|
+
const context = async () => {
|
|
111
|
+
if (behaviorOperation && house.registry.get(PAGE_BEHAVIOR_OPERATION) !== behaviorOperation) throw error('Native verifier operation was replaced.', 'CHROME_VERIFIER_CHANGED');
|
|
112
|
+
const value = await state();
|
|
113
|
+
if (!value.binding?.identity || sourceHash(await snapshot()) !== value.source_digest) throw error('Page/source binding is unavailable or stale.', 'CHROME_TASK_STALE_SOURCE');
|
|
114
|
+
return { task_digest: taskDigest, source_digest: value.source_digest, publication: { attempt_id: value.attempt_id, file_manifest_sha256: value.file_manifest_sha256, extension_id: value.deployment.extension_id }, binding: value.binding, verification: brief.verification, ...(casePlan ? { case: value.case_cursor || null } : {}) };
|
|
115
|
+
};
|
|
116
|
+
const freshBinding = async (options, value) => {
|
|
117
|
+
const targets = (await invoke(options, 'Targets')).targets;
|
|
118
|
+
if (!value.binding || !targets.some(target => target.target_id === value.binding.target_id && target.type === 'page' && target.url === value.binding.url)) throw error('The exact accepted page is missing or changed.', 'CHROME_TASK_TARGET_CHANGED');
|
|
119
|
+
const observed = await invoke(options, 'Contexts', { target_id: value.binding.target_id });
|
|
120
|
+
if (!observed.identity?.context_unique_id || value.binding.identity && digest(observed.identity) !== digest(value.binding.identity)) throw error('The accepted page document changed.', 'CHROME_TASK_DOCUMENT_CHANGED');
|
|
121
|
+
value.binding.identity = observed.identity;
|
|
122
|
+
if (sourceHash(await snapshot(options.signal)) !== value.source_digest) throw error('Source changed since publication.', 'CHROME_TASK_STALE_SOURCE');
|
|
123
|
+
return observed.identity;
|
|
124
|
+
};
|
|
125
|
+
let behaviorFrame = null, sourceAccess, caseCoordinator;
|
|
126
|
+
if (casePlan) caseCoordinator = createPageCaseCoordinator({
|
|
127
|
+
cases: casePlan, taskDigest, plan, store, sessionId, state, save, context, getOperation: name => house.registry.get(name), receiptLaw: pageLaw.receiptLaw,
|
|
128
|
+
async fresh(options, value) {
|
|
129
|
+
await freshBinding(options, value);
|
|
130
|
+
const remote = await invoke(options, 'Deployment', { project: brief.project, reconnect: false });
|
|
131
|
+
if (remote.status !== 'observed' || remote.attempt_id !== value.attempt_id || remote.file_manifest_sha256 !== value.file_manifest_sha256 || remote.extension_id !== value.deployment.extension_id) throw error('Physical case publication changed.', 'CHROME_TASK_STALE_SOURCE');
|
|
132
|
+
},
|
|
133
|
+
async runBehavior(options, input) {
|
|
134
|
+
behaviorFrame = { input, operation: house.registry.get(PAGE_BEHAVIOR_OPERATION) };
|
|
135
|
+
try {
|
|
136
|
+
const result = JSON.parse(await options.invoke(PAGE_BEHAVIOR_OPERATION, input, { signal: options.signal })), book = await store.load(sessionId);
|
|
137
|
+
const receipt = book.rows.findLast(row => row.type === 'house_receipt' && row.parent_invocation_id === options.invocation_id && row.operation === PAGE_BEHAVIOR_OPERATION && row.status === 'returned');
|
|
138
|
+
if (!receipt || receipt.output_truncated || receipt.output_digest !== digest(receipt.output) || result.context_digest !== digest(await context())) throw error('Case native evidence changed before settlement.');
|
|
139
|
+
return { result, receipt_id: receipt.invocation_id, output_digest: receipt.output_digest };
|
|
140
|
+
} finally { behaviorFrame = null; }
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const previousRun = async value => {
|
|
144
|
+
if (!value.behavior_receipt_id) return null;
|
|
145
|
+
const book = await store.load(sessionId);
|
|
146
|
+
const owner = book.rows.findLast(row => row.type === 'house_receipt' && row.invocation_id === (value.test_owner || value.behavior_owner) && row.operation === 'ChromeTest');
|
|
147
|
+
if (!owner || owner.status !== 'returned') throw error('The previous behavior coordinator did not complete; do not replay its effects.', 'CHROME_TASK_UNCERTAIN');
|
|
148
|
+
const receipt = book.rows.findLast(row => row.type === 'house_receipt' && row.invocation_id === value.behavior_receipt_id && row.operation === PAGE_BEHAVIOR_OPERATION);
|
|
149
|
+
const expectedLaw = pageLaw.receiptLaw(book, value.behavior_receipt_id);
|
|
150
|
+
if (!receipt || receipt.status !== 'returned' || receipt.parent_invocation_id !== value.behavior_owner || receipt.contract_digest !== behaviorOperation.digest || receipt.governance_plan_hash !== (typeof expectedLaw === 'string' ? expectedLaw : expectedLaw.plan_hash) || typeof expectedLaw !== 'string' && (receipt.mechanism_stamp !== expectedLaw.mechanism_stamp || receipt.governance_activation_id !== expectedLaw.activation_id) || receipt.output_truncated || receipt.output_digest !== digest(receipt.output)) throw error('The native behavior receipt is unavailable.', 'CHROME_TASK_UNCERTAIN');
|
|
151
|
+
const result = JSON.parse(receipt.output);
|
|
152
|
+
if (result.phase !== 'run' || result.context_digest !== digest(result.context) || digest(result.verifier) !== digest(verifier.descriptor) || digest(result.context) !== digest(await context())) throw error('Behavior source/document identity changed; publish a fresh candidate.', 'CHROME_TASK_STALE_EVIDENCE');
|
|
153
|
+
return { receipt_id: receipt.invocation_id, ...result };
|
|
154
|
+
};
|
|
155
|
+
const verify = async (options, semantic = false) => {
|
|
156
|
+
let value = await state(); assertReady(value);
|
|
157
|
+
if (!value.deployment) throw error('Publish the complete candidate first.');
|
|
158
|
+
await freshBinding(options, value);
|
|
159
|
+
const remote = await invoke(options, 'Deployment', { project: brief.project, reconnect: false });
|
|
160
|
+
if (remote.status !== 'observed' || remote.attempt_id !== value.attempt_id || remote.file_manifest_sha256 !== value.file_manifest_sha256 || remote.extension_id !== value.deployment.extension_id) throw error('Physical publication changed.', 'CHROME_TASK_STALE_SOURCE');
|
|
161
|
+
const primary = await invoke(options, 'Assert', { target_id: value.binding.target_id, selector: brief.fixture.selector, equals: brief.fixture.equals, timeout_ms: 0 });
|
|
162
|
+
if (primary.observed?.url !== value.binding.url) throw error('Primary observation belongs to another page.');
|
|
163
|
+
if (!semantic || !primary.passed) {
|
|
164
|
+
if (!primary.passed) { value.status = 'needs_correction'; value.verified_scope = null; await save(options, value); }
|
|
165
|
+
return { status: primary.passed ? 'verified' : 'needs_correction', scope: 'declared_page_dom', task_digest: taskDigest, source_digest: value.source_digest, binding: value.binding, observed: primary.observed, asked: 'unjudged' };
|
|
166
|
+
}
|
|
167
|
+
if (caseCoordinator) {
|
|
168
|
+
const result = await caseCoordinator.verify(options), current = await state();
|
|
169
|
+
return { ...result, task_digest: taskDigest, source_digest: current.source_digest, binding: current.binding };
|
|
170
|
+
}
|
|
171
|
+
const previous = await previousRun(value), phase = previous ? 'validate_final' : 'run';
|
|
172
|
+
if (previous?.status !== undefined && previous.status !== 'verified') return { status: 'needs_correction', scope: 'declared_page_behavior', behavior: previous, next_action: 'Correct application source and publish a new complete candidate. The failed test sequence is not replayed.', asked: 'unjudged' };
|
|
173
|
+
const input = { phase, context: await context(), previous };
|
|
174
|
+
behaviorFrame = { input, operation: house.registry.get(PAGE_BEHAVIOR_OPERATION) };
|
|
175
|
+
value.stage = 'behavior_pending'; value.status = 'ready'; await save(options, value);
|
|
176
|
+
let result, receipt;
|
|
177
|
+
try {
|
|
178
|
+
result = JSON.parse(await options.invoke(PAGE_BEHAVIOR_OPERATION, input, { signal: options.signal }));
|
|
179
|
+
const book = await store.load(sessionId);
|
|
180
|
+
receipt = book.rows.findLast(row => row.type === 'house_receipt' && row.parent_invocation_id === options.invocation_id && row.operation === PAGE_BEHAVIOR_OPERATION && row.status === 'returned');
|
|
181
|
+
if (!receipt || receipt.output_truncated || receipt.output_digest !== digest(receipt.output) || result.context_digest !== digest(await context())) throw error('Behavior evidence changed before acceptance.');
|
|
182
|
+
} finally { behaviorFrame = null; }
|
|
183
|
+
value = await state(); await freshBinding(options, value);
|
|
184
|
+
value.stage = 'checked'; value.status = result.status; value.verified_scope = result.status === 'verified' ? 'declared_page_behavior' : null;
|
|
185
|
+
if (phase === 'run') { value.behavior_receipt_id = receipt.invocation_id; value.behavior_owner = options.invocation_id; }
|
|
186
|
+
value.test_owner = options.invocation_id;
|
|
187
|
+
value.final_validation_receipt_id = phase === 'validate_final' && result.status === 'verified' ? receipt.invocation_id : null;
|
|
188
|
+
// A known failed run must be corrected by a new publication, not rerun against
|
|
189
|
+
// its already changed page. Keep its receipt for diagnosis in the checkpoint.
|
|
190
|
+
await save(options, value);
|
|
191
|
+
return { status: result.status, scope: 'declared_page_behavior', task_digest: taskDigest, source_digest: value.source_digest, binding: value.binding, verifier: verifier.descriptor, phase, behavior: result, receipt_id: receipt.invocation_id, asked: 'unjudged' };
|
|
192
|
+
};
|
|
193
|
+
const diagnosticOperations = new Map(['Deploy', 'Preview', 'Contexts'].map(name => [name, house.registry.get(prefix + name)]));
|
|
194
|
+
const publicationFailure = async (failure, options) => {
|
|
195
|
+
// v13 retains its original prospective proof/admission and error behavior.
|
|
196
|
+
// A failed diagnostic must never replace the original error or native mark.
|
|
197
|
+
if (recoverySelected || options.signal?.aborted) return;
|
|
198
|
+
try {
|
|
199
|
+
const book = await store.load(sessionId), currentSource = sourceHash(await snapshot(options.signal));
|
|
200
|
+
const facts = pagePublicationDiagnostic({ book, house, cwd, sessionId, task: brief, taskDigest, plan, sourceDigest: currentSource, operations: diagnosticOperations, activeOwner: options.invocation_id });
|
|
201
|
+
if (facts) markPagePublicationDiagnostic(failure, facts, { ...options, cwd });
|
|
202
|
+
} catch { /* Original failure remains authoritative; evidence is unavailable. */ }
|
|
203
|
+
};
|
|
204
|
+
const operationFrames = new AsyncLocalStorage(); let activeFrame = null;
|
|
205
|
+
const operation = (name, description, run, external = 'read', input_schema = empty) => ({ name, description, input_schema, ...(name === PAGE_CASE_OPERATION && caseRevision !== undefined ? { revision: caseRevision } : {}), steps: true, effects: { workspace: 'read', external, process: 'spawn', authority: 'none' }, async run(input, options) {
|
|
206
|
+
if (options.sessionId !== sessionId) throw error('Operation belongs to another page task.');
|
|
207
|
+
const inherited = operationFrames.getStore();
|
|
208
|
+
if (activeFrame && inherited !== activeFrame) throw error('Another page task operation is active; no effect was started.', 'CHROME_TASK_BUSY');
|
|
209
|
+
const dispatch = async () => {
|
|
210
|
+
try { return await (name === 'ChromeTest' && house.governance(sessionId)?.source_stamp ? pageLaw.bind(options).then(() => run(options, input)) : run(options, input)); }
|
|
211
|
+
catch (failure) { if (['ChromePublish', 'ChromeRecover'].includes(name)) await publicationFailure(failure, options); throw failure; }
|
|
212
|
+
};
|
|
213
|
+
if (inherited) return dispatch();
|
|
214
|
+
const frame = { id: options.invocation_id, signal: options.signal }; activeFrame = frame;
|
|
215
|
+
try { return await operationFrames.run(frame, dispatch); }
|
|
216
|
+
finally { if (activeFrame === frame) activeFrame = null; }
|
|
217
|
+
} });
|
|
218
|
+
house.registry.replace([...own, PAGE_BEHAVIOR_OPERATION, PAGE_CASE_OPERATION], [
|
|
219
|
+
pageBehaviorDefinition({ verifier, store, sessionId, directory, currentContext: context, async observeContext(identity, allowRenewal, receiptId, options) {
|
|
220
|
+
const value = await state(), before = value.binding.identity;
|
|
221
|
+
if (!identity?.context_unique_id || identity.connection_id !== before.connection_id || identity.browser_endpoint_id !== before.browser_endpoint_id || identity.frame_id !== before.frame_id) throw error('Verifier page identity changed unexpectedly.');
|
|
222
|
+
if (identity.context_unique_id === before.context_unique_id) return false;
|
|
223
|
+
if (!allowRenewal) throw error('Unexpected verifier document renewal.');
|
|
224
|
+
value.binding.identity = identity; await save(options, value);
|
|
225
|
+
await options.recordWork({ type: 'chrome_page_document_renewed', task_digest: taskDigest, before, after: identity, receipt_id: receiptId }); return true;
|
|
226
|
+
} }),
|
|
227
|
+
...(caseCoordinator ? [operation(PAGE_CASE_OPERATION, 'Internal accepted bounded page case; only its original native coordinator supplies the phase/index. No resident administration or uncertain replay.', (options, input) => caseCoordinator.executeCase(input, options), 'write', PAGE_CASE_INPUT)] : []),
|
|
228
|
+
operation('ChromePublish', 'Publish the complete accepted extension and open/renew only its immutable existing page. No popup. Children retain original authority; uncertain effects never replay.', async options => {
|
|
229
|
+
const old = await state(); assertReady(old); const before = await snapshot(options.signal);
|
|
230
|
+
if (old.deployment && sourceHash(before) === old.source_digest && ['published', 'checked'].includes(old.stage)) return verify(options);
|
|
231
|
+
const continuing = old.stage === 'reconciled';
|
|
232
|
+
if (continuing && sourceHash(before) !== old.source_digest) throw error('Recovered candidate changed.', 'CHROME_TASK_STALE_SOURCE');
|
|
233
|
+
const namespace = await invoke(options, 'Status');
|
|
234
|
+
const previewLease=publicationRecovery?await publicationRecovery.capture(options,namespace):undefined;
|
|
235
|
+
const value = continuing ? { ...old } : { stage: 'deploy_pending', status: 'ready', source_digest: sourceHash(before), file_manifest_sha256: physicalHash(before), attempt_id: randomUUID(), create_only: !namespace.extensions_truncated && !namespace.extensions.some(item => item.project === brief.project) };
|
|
236
|
+
await save(options, value);
|
|
237
|
+
if (!continuing) {
|
|
238
|
+
try { value.deployment = await invoke(options, 'Deploy', { project: brief.project, directory: brief.directory, attempt_id: value.attempt_id, expected_manifest_sha256: value.file_manifest_sha256, ...(value.create_only ? { create_only: true } : {}) }); }
|
|
239
|
+
catch (caught) { if (caught.code === 'CHROME_DEPLOY_PRECONDITION') { value.stage = 'source_rejected'; await save(options, value); } throw caught; }
|
|
240
|
+
}
|
|
241
|
+
if (value.deployment.project !== brief.project || value.deployment.attempt_id !== value.attempt_id || value.deployment.file_manifest_sha256 !== value.file_manifest_sha256) throw error('Deployment is not the accepted exact candidate.');
|
|
242
|
+
// A new publication gets a fresh owned page. Reuse/refresh only the exact old
|
|
243
|
+
// target; a lost acknowledgement remains pending and cannot be replayed.
|
|
244
|
+
// Preview servers live only for their original plugin connection. A new
|
|
245
|
+
// explicit source publication after restart opens a fresh owned fixture;
|
|
246
|
+
// it cannot reload the dead server or reuse the old semantic receipt.
|
|
247
|
+
if (old.binding && namespace.connection_id && namespace.connection_id === old.binding.identity?.connection_id) {
|
|
248
|
+
value.binding = structuredClone(old.binding); await save(options, value); await freshBinding(options, value);
|
|
249
|
+
value.stage = 'page_reload_pending'; await save(options, value);
|
|
250
|
+
const dispatched = await invoke(options, 'Interact', { target_id: value.binding.target_id, action: 'reload' });
|
|
251
|
+
if (dispatched.dispatched !== true) throw error('Page reload dispatch is unknown.', 'CHROME_TASK_UNCERTAIN');
|
|
252
|
+
const previous = value.binding.identity;
|
|
253
|
+
let observed; const until = Date.now() + 3000;
|
|
254
|
+
do {
|
|
255
|
+
try { observed = await invoke(options, 'Contexts', { target_id: value.binding.target_id }); }
|
|
256
|
+
catch (caught) { let detail; try { detail = JSON.parse(caught.message); } catch {} if (detail?.code !== 'CHROME_TARGET_NAVIGATING') throw caught; }
|
|
257
|
+
if (observed?.identity?.context_unique_id && observed.identity.context_unique_id !== previous.context_unique_id) break;
|
|
258
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
259
|
+
} while (Date.now() < until);
|
|
260
|
+
if (!observed?.identity?.context_unique_id || observed.identity.context_unique_id === previous.context_unique_id || observed.identity.connection_id !== previous.connection_id || observed.identity.browser_endpoint_id !== previous.browser_endpoint_id) throw error('New page document was not observed.', 'CHROME_TASK_UNCERTAIN');
|
|
261
|
+
value.binding.identity = observed.identity;
|
|
262
|
+
} else {
|
|
263
|
+
value.stage = 'page_open_pending'; await save(options, value);
|
|
264
|
+
const opened = await invoke(options, 'Preview', { project: brief.project, file: brief.fixture.file });
|
|
265
|
+
const url = new URL(opened.url);
|
|
266
|
+
if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || decodeURIComponent(url.pathname) !== '/' + brief.fixture.file) throw error('Preview returned another resource.');
|
|
267
|
+
value.binding = { target_id: opened.target_id, url: opened.url, project: brief.project, fixture_source_digest: fixtureDigest };
|
|
268
|
+
if(publicationRecovery)await publicationRecovery.acknowledge(options,value,opened,namespace,previewLease);
|
|
269
|
+
await save(options, value); await freshBinding(options, value);
|
|
270
|
+
if (old.binding) await options.recordWork({ type: 'chrome_page_publication_rebound', task_digest: taskDigest, previous: old.binding, binding: value.binding, reason: 'new_complete_source_publication_on_new_connection_no_old_semantic_credit' });
|
|
271
|
+
}
|
|
272
|
+
delete value.behavior_receipt_id; delete value.behavior_owner; delete value.test_owner; delete value.final_validation_receipt_id; delete value.verified_scope;
|
|
273
|
+
if (caseCoordinator) { delete value.case_series; delete value.case_progress; delete value.case_pending; delete value.case_cursor; delete value.case_final_owner; }
|
|
274
|
+
value.stage = 'published'; value.status = 'ready'; await save(options, value); return verify(options);
|
|
275
|
+
}, 'write'),
|
|
276
|
+
operation('ChromeVerify', 'Read the accepted page readiness criterion; this alone never closes semantic work.', options => verify(options)),
|
|
277
|
+
operation('ChromeTest', 'Run the pinned native page verifier once per publication; later calls only validate its completed terminal state by read-only ports. Known failures require corrected publication; unknown effects never replay.', options => verify(options, true), 'write'),
|
|
278
|
+
operation('ChromeFinish', 'Close only after native semantic evidence and fresh read-only terminal validation of this exact page/source/verifier.', async options => {
|
|
279
|
+
const current = workProjection(await store.load(sessionId));
|
|
280
|
+
if (current?.counts.done === 1) { const result = JSON.parse(await options.invoke('ChromeTest', {}, { signal: options.signal })); if (result.status !== 'verified') throw error('Current semantic state failed.', 'WORK_UNVERIFIED'); return { status: 'done', asked: 'unjudged' }; }
|
|
281
|
+
if (!(await state()).behavior_receipt_id) { const result = JSON.parse(await options.invoke('ChromeTest', {}, { signal: options.signal })); if (result.status !== 'verified') throw error('Semantic behavior failed.', 'WORK_UNVERIFIED'); }
|
|
282
|
+
await options.invoke('WorkStepRun', { task_id: 'chrome-task', plan_version: current.plan.version, step_id: 'browser', recheck: true }, { signal: options.signal });
|
|
283
|
+
await options.invoke('WorkTaskUpdate', { task_id: 'chrome-task', plan_version: current.plan.version, status: 'done' }, { signal: options.signal }); return { status: 'done', asked: 'unjudged' };
|
|
284
|
+
}),
|
|
285
|
+
operation('ChromeDeliver', 'Publish this complete page extension, return bounded failed native facts for correction, and close only after semantic acceptance plus read-only final validation.', async options => {
|
|
286
|
+
const publication = JSON.parse(await options.invoke('ChromePublish', {}, { signal: options.signal }));
|
|
287
|
+
if (publication.status !== 'verified') return { status: 'needs_correction', stage: 'page_readiness', publication, asked: 'unjudged' };
|
|
288
|
+
const check = JSON.parse(await options.invoke('ChromeTest', {}, { signal: options.signal }));
|
|
289
|
+
if (check.status !== 'verified') return { status: 'needs_correction', stage: 'declared_page_behavior', check, next_action: 'Correct only application source from these native facts, then publish the complete candidate with ChromeDeliver.', asked: 'unjudged' };
|
|
290
|
+
await options.invoke('ChromeFinish', {}, { signal: options.signal });
|
|
291
|
+
const value = await state(); return { status: 'done', scope: 'declared_page_behavior_and_source_snapshot', task_digest: taskDigest, source_digest: value.source_digest, binding: value.binding, receipt_id: value.final_validation_receipt_id, asked: 'unjudged' };
|
|
292
|
+
}, 'write'),
|
|
293
|
+
operation('ChromeInspect', 'Read bounded live DOM on the one accepted published page. Publish the complete candidate first with ChromePublish or ChromeDeliver. Missing prerequisites return without opening a page; interrupted effects remain blocked. No target/expression arguments and no verification credit.', async options => {
|
|
294
|
+
const value = await state(); assertReady(value);
|
|
295
|
+
if (!value.deployment && !value.binding) return { status: 'not_published', next_action: 'ChromePublish', asked: 'unjudged' };
|
|
296
|
+
if (value.stage === 'reconciled' && value.deployment && !value.binding) return { status: 'unavailable', reason: 'page_not_opened', next_action: 'ChromePublish', asked: 'unjudged' };
|
|
297
|
+
await freshBinding(options, value);
|
|
298
|
+
return { status: 'observed', scope: 'untrusted_owned_page', observation: await invoke(options, 'ReadDOM', { target_id: value.binding.target_id, selector: 'body' }) };
|
|
299
|
+
}),
|
|
300
|
+
operation('ChromeRecover', 'Before any publication attempt, return not_published without native work. Otherwise read-only reconciliation of a known Deploy attempt before any page open/reload. Later uncertain page/test effects stay blocked; never replays them.' + (recoverySelected?' Explicit v13 may reconcile an acknowledged Preview/read failure on its original live transport and unchanged authority, with bounded metadata reads and no reconnect/replay or semantic credit.':'') + (caseCoordinator ? ' An original returned Case owner may be settled by reads after a checkpoint failure; a returned native child without its owner is insufficient.' : ''), async options => {
|
|
301
|
+
const old = await state();
|
|
302
|
+
if (old.stage === 'new' && old.status === 'ready' && Object.keys(old).length === 2) {
|
|
303
|
+
const book = await store.load(sessionId), start = book.rows.findLastIndex(row => row.type === 'chrome_task_accepted' && row.source === 'workflow_host');
|
|
304
|
+
if (!book.journalAudit.consistent || book.rows[start]?.task_digest !== taskDigest) throw error('Accepted page task journal changed.', 'AUDIT_INVALID');
|
|
305
|
+
// State can still default to new after an interrupted first checkpoint.
|
|
306
|
+
// Only an untouched publication history establishes this prerequisite.
|
|
307
|
+
const attempted = book.rows.slice(start + 1).some(row => row.type === 'chrome_task_checkpoint' || row.type.startsWith('chrome_page_') || row.type === 'house_proposal' && (['ChromePublish', 'ChromeDeliver', PAGE_CASE_OPERATION, PAGE_BEHAVIOR_OPERATION].includes(row.operation) || row.operation.startsWith(prefix)));
|
|
308
|
+
if (!attempted) return { status: 'not_published', next_action: 'ChromePublish', asked: 'unjudged' };
|
|
309
|
+
}
|
|
310
|
+
if(publicationRecovery&&old.stage==='page_open_pending')return publicationRecovery.recover(options,old);
|
|
311
|
+
if (caseCoordinator && old.stage === 'case_pending') return caseCoordinator.recover(options, old);
|
|
312
|
+
if (!['deploy_pending', 'reconciled'].includes(old.stage) || !old.attempt_id || sourceHash(await snapshot(options.signal)) !== old.source_digest) throw error('This page checkpoint cannot be replayed or automatically recovered.', 'CHROME_TASK_UNCERTAIN');
|
|
313
|
+
const remote = await invoke(options, 'Deployment', { project: brief.project });
|
|
314
|
+
if (remote.status !== 'observed' || remote.project !== brief.project || remote.attempt_id !== old.attempt_id || remote.file_manifest_sha256 !== old.file_manifest_sha256 || !remote.extension_id) throw error('Exact publication is unavailable.', 'CHROME_TASK_UNCERTAIN');
|
|
315
|
+
const value = { ...old, deployment: remote, stage: 'reconciled', status: 'ready' }; await save(options, value);
|
|
316
|
+
return { status: 'recovered', next_action: 'ChromePublish', claim: 'publication_only_not_functional_delivery', asked: 'unjudged' };
|
|
317
|
+
}),
|
|
318
|
+
]);
|
|
319
|
+
behaviorOperation = house.registry.get(PAGE_BEHAVIOR_OPERATION);
|
|
320
|
+
caseCoordinator?.bind(house.registry.get(PAGE_CASE_OPERATION), behaviorOperation);
|
|
321
|
+
registerWorkOperations({ store, house, artifactObservation: { sessionId, operation: 'FileInfo' } });
|
|
322
|
+
if (!saved) await store.append(sessionId, { type: 'chrome_task_accepted', source: 'workflow_host', acceptance: source, ...(provenance ? { provenance } : {}), task: brief, task_digest: taskDigest, governance_plan_hash: plan.plan_hash, governance_profile: plan.profile, fixture_source_digest: fixtureDigest, supplied_inputs_digest: suppliedDigest, artifact_observation: { operation: 'FileInfo', hash: true, paths: [artifact] } });
|
|
323
|
+
const sourcePath = async (file, mutable) => {
|
|
324
|
+
const absolute = path.resolve(cwd, file), relative = path.relative(directory, absolute);
|
|
325
|
+
if (!relative || path.isAbsolute(relative) || relative.split(path.sep).some(part => part === '..' || ['.git', '.comal', '.harness', '.ssh', 'node_modules'].includes(part) || /^\.env(?:\.|$)/i.test(part))) return false;
|
|
326
|
+
if (absolute === path.join(directory, brief.fixture.file) || mutable && isSuppliedPath(relative, brief.supplied_inputs)) return false;
|
|
327
|
+
await validateTaskDirectory(cwd, brief.directory); await verifySuppliedInputs(directory, brief.supplied_inputs);
|
|
328
|
+
let ancestor = absolute;
|
|
329
|
+
for (;;) { try { return await realpath(ancestor) === ancestor; } catch (caught) { if (caught.code !== 'ENOENT') throw caught; ancestor = path.dirname(ancestor); } }
|
|
330
|
+
};
|
|
331
|
+
// Keep original host catalogue/shutdown lifecycles available without making
|
|
332
|
+
// every human/internal operation an exception to accepted page authority.
|
|
333
|
+
const runtimeControls = new Map(['McpSync', 'McpClose', 'PluginClose'].map(name => [name, house.registry.get(name)]).filter(([, op]) => op?.contract.origin === null));
|
|
334
|
+
const scope = async view => {
|
|
335
|
+
if (policy.denied.has(view.operation)) return false;
|
|
336
|
+
const control = runtimeControls.get(view.operation);
|
|
337
|
+
if (view.surface === 'internal' && control && house.registry.get(view.operation) === control && view.contract_digest === control.digest) return true;
|
|
338
|
+
if (view.operation === PAGE_CASE_OPERATION) return Boolean(caseCoordinator?.admit(view));
|
|
339
|
+
if (view.operation === PAGE_BEHAVIOR_OPERATION) return Boolean(behaviorFrame && view.parent_operation === (caseCoordinator ? PAGE_CASE_OPERATION : 'ChromeTest') && view.contract_digest === behaviorFrame.operation.digest && digest(view.input) === digest(behaviorFrame.input));
|
|
340
|
+
if (view.parent_operation === PAGE_BEHAVIOR_OPERATION) {
|
|
341
|
+
const frame = behaviorFrame, name = view.operation.startsWith(prefix) ? view.operation.slice(prefix.length) : null;
|
|
342
|
+
return Boolean(frame && PAGE_VERIFIER_CONTRACT[frame.input.phase === 'run' ? 'run_ports' : 'final_ports'].includes(name) && view.input.target_id === (await state()).binding?.target_id && (name !== 'InspectValue' || view.input.context_unique_id === (await state()).binding.identity.context_unique_id));
|
|
343
|
+
}
|
|
344
|
+
if (['WorkPlanSet', 'WorkCheckRepair'].includes(view.operation)) return false;
|
|
345
|
+
if (['Read', 'Write', 'Edit'].includes(view.operation)) return sourcePath(view.input.file_path, view.operation !== 'Read');
|
|
346
|
+
if (CHROME_SOURCE_NAMES.includes(view.operation)) return Boolean(sourceAccess?.active() && await sourceAccess.admit(view));
|
|
347
|
+
if (view.operation === 'FileInfo') return true; // Separate narrow artifact gate.
|
|
348
|
+
if (own.includes(view.operation)) return true;
|
|
349
|
+
if (['WorkStepRun', 'WorkTaskUpdate'].includes(view.operation)) return ['ChromeFinish', 'WorkTaskUpdate'].includes(view.parent_operation);
|
|
350
|
+
if (!view.operation.startsWith(prefix) || !(own.includes(view.parent_operation) || caseCoordinator && view.parent_operation === PAGE_CASE_OPERATION)) return false;
|
|
351
|
+
const value = await state(), name = view.operation.slice(prefix.length), input = view.input;
|
|
352
|
+
if(publicationRecovery&&view.parent_operation==='ChromeRecover'&&value.stage==='page_open_pending')return publicationRecovery.admit(view);
|
|
353
|
+
if (name === 'Status') return view.parent_operation === 'ChromePublish';
|
|
354
|
+
if (name === 'Deploy') return view.parent_operation === 'ChromePublish' && input.project === brief.project && input.directory === brief.directory && input.attempt_id === value.attempt_id && input.expected_manifest_sha256 === value.file_manifest_sha256 && Boolean(input.create_only) === Boolean(value.create_only);
|
|
355
|
+
if (name === 'Deployment') return [...['ChromePublish', 'ChromeVerify', 'ChromeTest', 'ChromeRecover'], ...(caseCoordinator ? [PAGE_CASE_OPERATION] : [])].includes(view.parent_operation) && input.project === brief.project;
|
|
356
|
+
if (name === 'Preview') return view.parent_operation === 'ChromePublish' && value.stage === 'page_open_pending' && input.project === brief.project && input.file === brief.fixture.file;
|
|
357
|
+
if (name === 'Targets') return true;
|
|
358
|
+
if (input.target_id !== value.binding?.target_id) return false;
|
|
359
|
+
if (name === 'Contexts') return true;
|
|
360
|
+
if (name === 'Assert') return input.selector === brief.fixture.selector && input.equals === brief.fixture.equals;
|
|
361
|
+
if (name === 'ReadDOM') return view.parent_operation === 'ChromeInspect' && input.selector === 'body';
|
|
362
|
+
if (name === 'Interact') return view.parent_operation === 'ChromePublish' && value.stage === 'page_reload_pending' && input.action === 'reload' && !input.selector && !input.text;
|
|
363
|
+
return false;
|
|
364
|
+
};
|
|
365
|
+
const close = async view => {
|
|
366
|
+
if (view.input.status !== 'done') return true;
|
|
367
|
+
const value = await state();
|
|
368
|
+
if (view.parent_operation !== 'ChromeFinish' || value.status !== 'verified' || value.verified_scope !== 'declared_page_behavior' || !value.final_validation_receipt_id || sourceHash(await snapshot()) !== value.source_digest) return false;
|
|
369
|
+
// The page can change after WorkStepRun, including within the same document.
|
|
370
|
+
// Reuse the completed run through the original executor: ChromeTest now only
|
|
371
|
+
// reads terminal semantics and never replays the run's native mutations.
|
|
372
|
+
let current;
|
|
373
|
+
try { current = JSON.parse(await executor.execute('ChromeTest', {}, { signal: activeFrame?.signal })); }
|
|
374
|
+
catch (failure) {
|
|
375
|
+
const code = failure?.code;
|
|
376
|
+
if (typeof code !== 'string' || !Object.hasOwn(closeRefusals, code)) throw failure;
|
|
377
|
+
return nativeClosureRefusal(failure,{ allowed: false, reason: `Fresh page closure refused (${code}): ${closeRefusals[code]}` });
|
|
378
|
+
}
|
|
379
|
+
return current.status === 'verified' && current.phase === 'validate_final' && current.source_digest === value.source_digest && sourceHash(await snapshot()) === value.source_digest;
|
|
380
|
+
};
|
|
381
|
+
if (!workProjection(await store.load(sessionId))) await executor.execute('WorkPlanSet', { objective: `Verify existing-page task ${brief.project} (${taskDigest})`, expected_version: 0, tasks: [declaredTask] });
|
|
382
|
+
const initial = workProjection(await store.load(sessionId));
|
|
383
|
+
if (initial.current?.status === 'pending') await executor.execute('WorkTaskUpdate', { task_id: 'chrome-task', plan_version: initial.plan.version, status: 'in_progress' });
|
|
384
|
+
sourceAccess = bindChromeSource({ executor, store, sessionId, cwd, plan, taskDigest, pathAllowed: sourcePath, directory: brief.directory });
|
|
385
|
+
await house.activateGovernance({ sessionId, plan, bindings: {
|
|
386
|
+
'runtime:chrome-task.scope': { revision: 'chrome-page-scope-v1', operations: ['*'], configuration_digest: taskDigest, check: scope },
|
|
387
|
+
'runtime:chrome-task.close': { revision: 'chrome-page-close-v1', operations: ['WorkTaskUpdate'], configuration_digest: taskDigest, check: close },
|
|
388
|
+
'runtime:chrome-task.artifact-hash': { revision: 'chrome-page-artifact-v1', operations: ['FileInfo'], configuration_digest: digest({ taskDigest, artifact, contract_digest: hashOp.digest }), check: async view => !policy.denied.has('FileInfo') && view.contract_digest === hashOp.digest && (['WorkStepRun', 'WorkTaskUpdate'].includes(view.parent_operation) && digest(view.input) === digest({ file_path: artifact, hash: true }) || await sourceAccess.inspection(view)) },
|
|
389
|
+
} });
|
|
390
|
+
const project = frame => {
|
|
391
|
+
const definitions = frame.definitions().filter(tool => publicNames.has(tool.name)).map(tool => {
|
|
392
|
+
if (!['Read', 'Write', 'Edit', 'SourceDraft'].includes(tool.name)) return tool;
|
|
393
|
+
const view = structuredClone(tool);
|
|
394
|
+
// Accepted path facts are bounded by the task schema. They describe the
|
|
395
|
+
// existing coordinate system; schemas and native source authority stay intact.
|
|
396
|
+
view.description += '\nPage-task path facts: ' + JSON.stringify({ accepted_directory: brief.directory }) + '. Use workspace-relative file paths under that directory; a bare filename is not relative to it. Equivalent native-valid absolute/relative paths remain valid. The fixture and verifier/oracle data are not source Read targets. Supplied inputs are read-only: do not Write, Edit or SourceDraft them.';
|
|
397
|
+
if (tool.name === 'Read') {
|
|
398
|
+
const calls = brief.supplied_inputs.filter(item => item.file.toLowerCase() !== brief.fixture.file.toLowerCase()).map(item => ({ name: 'Read', input: { file_path: path.posix.join(brief.directory, item.file) } }));
|
|
399
|
+
view.description += ' Read takes a file, not a directory. Exact supplied-file Read calls: ' + JSON.stringify(calls) + '.';
|
|
400
|
+
} else view.description += ' Exact supplied-file paths are shown as Read calls on Read.';
|
|
401
|
+
return view;
|
|
402
|
+
}), names = new Set(definitions.map(tool => tool.name));
|
|
403
|
+
return { ...frame, definitions: () => definitions, execute(name, input, options = {}) { if (options.modelProposed && !names.has(name)) throw error('Only accepted page-task tools are offered.', 'HOUSE_SURFACE_CHANGED'); return frame.execute(name, input, options.modelProposed ? { ...options, modelSurface: [...names].filter(item => !options.modelSurface || options.modelSurface.includes(item)) } : options); } };
|
|
404
|
+
};
|
|
405
|
+
return {
|
|
406
|
+
task: brief, taskDigest, plan, pinnedSchemas: [...CHROME_SOURCE_NAMES],
|
|
407
|
+
checkpointProjectionStatus:()=>({requested:Boolean(selectedStateReader),effective:Boolean(pageStateReader),fallback:recoverySelected?'v13_original_full_book':null,reader:selectedStateReader?.status()??null}),
|
|
408
|
+
system: `Host-managed existing-page Chrome task ${JSON.stringify(brief)}. No popup is required. Implement only mutable extension application source; the fixture is hidden/read-only and supplied assets are immutable. SourceDraft/Retain/Read/Publish retain bounded proposals without regeneration. Prefer ChromeDeliver after complete source edits. It returns native failed semantic facts while work stays open; correct source and deliver again. ChromeVerify readiness alone is not acceptance. Final validation reads only; interrupted test effects are never replayed. ${casePlan ? 'The house owns ordered bounded cases and their native receipts; no case IDs or work administration need to be supplied. ChromeRecover may read-reconcile a returned original Case owner; missing owners remain blocked. ' : ''}${recoverySelected?'Explicit v13 ChromeRecover may reconcile only an acknowledged Preview/read failure on the original live transport and unchanged authority. publication_ready_unverified has no semantic credit; continue with ChromeTest or ChromeDeliver. This bounded workflow has a fixed host catalogue; MCP tools/catalogue refresh are unavailable. ':''}No raw browser target or JavaScript tool is offered. Report delivery only after ChromeDeliver/Finish returns done; human acceptance remains unjudged.`,
|
|
409
|
+
wrap: tools => ({ ...tools, house, policy, workflow: 'chrome-task', definitions: () => project(tools.snapshot()).definitions(), snapshot: () => project(tools.snapshot()), execute: (name, input, options) => project(tools.snapshot()).execute(name, input, options) }),
|
|
410
|
+
async completedResult(calls = []) {
|
|
411
|
+
if (!calls.some(call => call.name === 'ChromeDeliver')) return null;
|
|
412
|
+
const book = await store.load(sessionId), receipt = book.rows.findLast(row => row.type === 'house_receipt' && row.operation === 'ChromeDeliver' && row.status === 'returned' && calls.some(call => call.id === row.call_id && call.name === 'ChromeDeliver'));
|
|
413
|
+
if (!receipt || !book.journalAudit.verified || workProjection(book)?.counts.done !== 1) return null;
|
|
414
|
+
const result = JSON.parse(receipt.output);
|
|
415
|
+
if (result.status !== 'done' || result.task_digest !== taskDigest || sourceHash(await snapshot()) !== result.source_digest) return null;
|
|
416
|
+
return { terminal: true, result: `Existing-page extension verified: ${brief.directory}. Native semantic evidence ${result.receipt_id}; source ${result.source_digest}. Human acceptance remains unjudged.`, receipt_id: receipt.invocation_id };
|
|
417
|
+
},
|
|
418
|
+
async finishResult(final, baseline) {
|
|
419
|
+
let delivery = { status: 'open', task_digest: taskDigest, governance_plan_hash: plan.plan_hash, asked: 'unjudged' };
|
|
420
|
+
const book = await store.load(sessionId), closed = book.rows.slice(baseline.baseline).findLast(row => row.type === 'house_receipt' && row.operation === 'ChromeFinish' && row.status === 'returned');
|
|
421
|
+
if (!final.is_error && !final.work_stop_reason && closed) {
|
|
422
|
+
try { const value = JSON.parse(await executor.execute('ChromeTest', {}, { signal: baseline.signal })); if (value.status !== 'verified') throw error('Final page semantics failed.'); delivery = { ...delivery, status: 'verified', scope: 'declared_page_behavior_and_source_snapshot', receipt_id: value.receipt_id, source_digest: value.source_digest, binding: value.binding }; }
|
|
423
|
+
catch (caught) { delivery = { ...delivery, status: 'needs_verification', code: caught.code || 'CHROME_TASK_FINAL_CHECK' }; final.is_error = true; final.work_stop_reason = 'verification_failed'; }
|
|
424
|
+
}
|
|
425
|
+
final.delivery = delivery; await store.append(sessionId, { type: 'chrome_task_delivery', source: 'workflow_host', ...delivery });
|
|
426
|
+
},
|
|
427
|
+
dispose() { sourceAccess.dispose(); house.retireGovernance(sessionId); house.registry.replace([...own, PAGE_BEHAVIOR_OPERATION, PAGE_CASE_OPERATION], []); },
|
|
428
|
+
};
|
|
429
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Trusted startup code, not a sandbox or a model-loadable plugin mechanism.
|
|
2
|
+
import { readFile, realpath, lstat } from 'node:fs/promises';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { Script } from 'node:vm';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { digest, immutable } from '../operations.mjs';
|
|
7
|
+
|
|
8
|
+
export const PAGE_BEHAVIOR_OPERATION = 'ChromePageBehavior';
|
|
9
|
+
const SHA = /^[a-f0-9]{64}$/, prefix = 'plugin__chrome-lab__';
|
|
10
|
+
const fail = message => Object.assign(new Error(message), { code: 'CHROME_VERIFIER_CHANGED' });
|
|
11
|
+
export const PAGE_VERIFIER_CONTRACT = immutable({ version: 1, operation: PAGE_BEHAVIOR_OPERATION, phases: ['run', 'validate_final'], run_ports: ['Contexts', 'Assert', 'ReadDOM', 'Console', 'Evaluate', 'InspectValue', 'Interact'], final_ports: ['Contexts', 'Console', 'InspectValue'], max_calls: 160, max_result_bytes: 8000, timeout_ms: 60000 });
|
|
12
|
+
export const PAGE_VERIFIER_CONTRACT_DIGEST = digest(PAGE_VERIFIER_CONTRACT);
|
|
13
|
+
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
14
|
+
const registered = new WeakSet();
|
|
15
|
+
|
|
16
|
+
// One exact function-expression source owns its closure. Relative imports are not
|
|
17
|
+
// a supported source format. The human trusts all of this Node code explicitly;
|
|
18
|
+
// this does not sandbox dishonest host code or attest ambient external resources.
|
|
19
|
+
export function compileChromePageVerifier(source, { trusted = false, file = null } = {}) {
|
|
20
|
+
if (!trusted) throw fail('Explicit startup host-code trust is required for a Chrome page verifier.');
|
|
21
|
+
if (Buffer.byteLength(source) > 262144) throw fail('Chrome verifier source exceeds 256 KiB.');
|
|
22
|
+
let implementation = null, initializing = null;
|
|
23
|
+
const implementationSha256 = hash(source), descriptor = immutable({ implementation_sha256: implementationSha256, contract_digest: PAGE_VERIFIER_CONTRACT_DIGEST });
|
|
24
|
+
const compiled = Object.freeze({
|
|
25
|
+
descriptor, file,
|
|
26
|
+
async prepare(directory, verification) {
|
|
27
|
+
assertPageVerifier(compiled, verification); await compiled.assertCurrent(directory);
|
|
28
|
+
if (implementation) return;
|
|
29
|
+
initializing ||= Promise.resolve().then(() => {
|
|
30
|
+
// Identity and source location have already been admitted. Even evaluating
|
|
31
|
+
// an outer IIFE from a changed file is forbidden before this point.
|
|
32
|
+
const factory = new Script('(' + source + '\n)', { filename: file || 'trusted-chrome-page-verifier' }).runInThisContext();
|
|
33
|
+
if (typeof factory !== 'function') throw fail('Verifier source must be one factory function expression.');
|
|
34
|
+
const value = factory();
|
|
35
|
+
if (!value || typeof value.run !== 'function' || typeof value.validate_final !== 'function') throw fail('Verifier factory requires run and validate_final callbacks.');
|
|
36
|
+
implementation = Object.freeze(value);
|
|
37
|
+
});
|
|
38
|
+
await initializing;
|
|
39
|
+
},
|
|
40
|
+
async assertCurrent(directory) {
|
|
41
|
+
if (!file) return;
|
|
42
|
+
const absolute = path.resolve(file), relative = path.relative(directory, absolute);
|
|
43
|
+
if (!relative || !relative.startsWith('..' + path.sep) && relative !== '..' && !path.isAbsolute(relative)) throw fail('Host verifier source must be outside resident extension source.');
|
|
44
|
+
const info = await lstat(absolute);
|
|
45
|
+
if (!info.isFile() || info.size > 262144 || await realpath(absolute) !== absolute || hash(await readFile(absolute)) !== implementationSha256) throw fail('The accepted verifier source identity changed.');
|
|
46
|
+
},
|
|
47
|
+
async execute(phase, context, previous, port) {
|
|
48
|
+
if (!implementation || !PAGE_VERIFIER_CONTRACT.phases.includes(phase)) throw fail('Verifier was not admitted or its phase is unknown.');
|
|
49
|
+
return implementation[phase](immutable(structuredClone({ context, criterion: context.verification.criteria, previous })), port);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
registered.add(compiled); return compiled;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function loadChromePageVerifier(file, { trusted = false } = {}) {
|
|
56
|
+
if (!trusted) throw fail('--chrome-verifier executes trusted host code and requires --trust-plugins.');
|
|
57
|
+
const absolute = path.resolve(file), info = await lstat(absolute);
|
|
58
|
+
if (!info.isFile() || info.size > 262144 || await realpath(absolute) !== absolute) throw fail('Verifier must be a bounded regular file without symlink aliases.');
|
|
59
|
+
const source = await readFile(absolute, 'utf8');
|
|
60
|
+
return compileChromePageVerifier(source, { trusted, file: absolute });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function assertPageVerifier(verifier, verification) {
|
|
64
|
+
if (!registered.has(verifier) || !SHA.test(verification.implementation_sha256) || digest(verifier.descriptor) !== digest({ implementation_sha256: verification.implementation_sha256, contract_digest: verification.contract_digest }) || verification.criteria_digest !== digest(verification.criteria)) throw fail('The accepted verifier code/contract/criteria identity is unavailable or changed.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function pageBehaviorDefinition({ verifier, store, sessionId, directory, currentContext, observeContext }) {
|
|
68
|
+
return {
|
|
69
|
+
name: PAGE_BEHAVIOR_OPERATION, description: 'Internal identity-bound page behavior verifier; only the native task coordinator supplies its resource and phase.',
|
|
70
|
+
input_schema: { type: 'object', required: ['phase', 'context', 'previous'], properties: { phase: { enum: ['run', 'validate_final'] }, context: { type: 'object' }, previous: { type: ['object', 'null'] } }, additionalProperties: false },
|
|
71
|
+
steps: true, revision: digest(verifier.descriptor), effects: { workspace: 'read', external: 'write', process: 'spawn', authority: 'none' },
|
|
72
|
+
async run({ phase, context, previous }, options) {
|
|
73
|
+
const lease = new AbortController();
|
|
74
|
+
const signal = AbortSignal.any([lease.signal, ...(options.signal ? [options.signal] : []), AbortSignal.timeout(PAGE_VERIFIER_CONTRACT.timeout_ms)]);
|
|
75
|
+
if (options.sessionId !== sessionId || digest(context) !== digest(await currentContext())) throw fail('Verifier resource binding changed.');
|
|
76
|
+
assertPageVerifier(verifier, context.verification); await verifier.assertCurrent(directory);
|
|
77
|
+
let bound = context, pendingReload = false, calls = 0, active = true, inFlight = 0;
|
|
78
|
+
const receipts = [], permitted = PAGE_VERIFIER_CONTRACT[phase === 'run' ? 'run_ports' : 'final_ports'];
|
|
79
|
+
const port = Object.freeze({ signal, context: () => structuredClone(bound), async call(name, input = {}) {
|
|
80
|
+
if (!active) throw fail('Verifier phase has ended; its child port cannot be reused.');
|
|
81
|
+
signal.throwIfAborted();
|
|
82
|
+
inFlight++;
|
|
83
|
+
try {
|
|
84
|
+
if (!permitted.includes(name) || ++calls > PAGE_VERIFIER_CONTRACT.max_calls || input.target_id && input.target_id !== bound.binding.target_id || pendingReload && name !== 'Contexts' || name === 'Interact' && !['click', 'fill', 'reload'].includes(input.action)) throw fail('Verifier child exceeds its accepted phase/resource.');
|
|
85
|
+
if (digest(bound) !== digest(await currentContext())) throw fail('Verifier source or resource changed before a child.');
|
|
86
|
+
await verifier.assertCurrent(directory);
|
|
87
|
+
if (!active) throw fail('Verifier callback ended before its native child was dispatched.');
|
|
88
|
+
signal.throwIfAborted();
|
|
89
|
+
const args = { ...input, target_id: bound.binding.target_id, ...(name === 'InspectValue' ? { context_unique_id: bound.binding.identity.context_unique_id } : {}) };
|
|
90
|
+
const output = JSON.parse(await options.invoke(prefix + name, args, { signal }));
|
|
91
|
+
const book = await store.load(sessionId), receipt = book.rows.findLast(row => row.type === 'house_receipt' && row.parent_invocation_id === options.invocation_id && row.operation === prefix + name && row.status === 'returned');
|
|
92
|
+
if (!receipt || receipt.arguments_digest !== digest(args) || receipt.output_truncated || receipt.output_digest !== digest(receipt.output)) throw fail('Native verifier child receipt is unavailable or incomplete.');
|
|
93
|
+
receipts.push(receipt.invocation_id);
|
|
94
|
+
if (name === 'Interact' && input.action === 'reload') {
|
|
95
|
+
if (output.dispatched !== true) throw fail('Reload dispatch is unknown; do not replay.'); pendingReload = true;
|
|
96
|
+
}
|
|
97
|
+
if (name === 'Contexts') {
|
|
98
|
+
const renewed = await observeContext(output.identity, phase === 'run' && pendingReload, receipt.invocation_id, options);
|
|
99
|
+
bound = await currentContext(); if (renewed) pendingReload = false;
|
|
100
|
+
}
|
|
101
|
+
return output;
|
|
102
|
+
} finally { inFlight--; }
|
|
103
|
+
} });
|
|
104
|
+
let rejectAborted;
|
|
105
|
+
const aborted = new Promise((resolve, reject) => { rejectAborted = () => reject(signal.reason); signal.addEventListener('abort', rejectAborted, { once: true }); if (signal.aborted) rejectAborted(); });
|
|
106
|
+
let result;
|
|
107
|
+
try {
|
|
108
|
+
result = await Promise.race([verifier.execute(phase, context, previous, port), aborted]);
|
|
109
|
+
active = false;
|
|
110
|
+
if (inFlight) throw fail('Verifier returned with an unawaited native child; effects are uncertain and cannot be credited.');
|
|
111
|
+
signal.throwIfAborted(); await verifier.assertCurrent(directory);
|
|
112
|
+
if (pendingReload || digest(bound) !== digest(await currentContext()) || !result || !['verified', 'needs_correction'].includes(result.status) || !receipts.length || Buffer.byteLength(JSON.stringify(result)) > PAGE_VERIFIER_CONTRACT.max_result_bytes) throw fail('Verifier returned unknown, empty, oversized or stale evidence.');
|
|
113
|
+
const output = { status: result.status, facts: result.facts ?? null, terminal: result.terminal ?? null, phase, context_digest: digest(bound), context: bound, evidence: receipts, verifier: verifier.descriptor };
|
|
114
|
+
if (Buffer.byteLength(JSON.stringify(output)) > 24000) throw fail('Complete verifier evidence exceeds the native bound.');
|
|
115
|
+
return output;
|
|
116
|
+
} finally {
|
|
117
|
+
active = false;
|
|
118
|
+
signal.removeEventListener('abort', rejectAborted);
|
|
119
|
+
lease.abort(fail('Verifier phase ended; no continuing native child authority.'));
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|