// Host implementation of TCS-DARK-CORE-EXECUTION-0001. No model or authority setter. import fs from 'node:fs'; import path from 'node:path'; import { createHash } from 'node:crypto'; export const digest = value => createHash('sha256').update(JSON.stringify(value)).digest('hex'); const copy = value => structuredClone(value); const terminal = new Set(['COMPLETED', 'STOPPED', 'WITHDRAWN', 'FAILED', 'RECOVERY_REQUIRED']); const text = value => typeof value === 'string' && value.trim().length > 0; const deepFreeze = value => { if (value && typeof value === 'object') { Object.values(value).forEach(deepFreeze); Object.freeze(value); } return value; }; export class DarkCoreTask { #plan; #state; #host; #file; #busy = false; #abort; #epoch = 0; #pending; constructor({ plan, directory, host }) { if (plan?.channel !== 'ICE-CH-DK001' || !text(plan.id) || !text(plan.goal) || !text(plan.authorizationRef) || !Array.isArray(plan.steps) || !plan.steps.length || !plan.steps.every(s => text(s.id) && text(s.capability) && s.args && typeof s.args === 'object') || new Set(plan.steps.map(s => s.id)).size !== plan.steps.length) throw Error('INVALID_PLAN'); for (const method of ['authorize', 'execute', 'verify', 'verifyHuman']) { if (typeof host?.[method] !== 'function') throw Error(`HOST_ADAPTER_REQUIRED:${method}`); } if (!path.isAbsolute(directory)) throw Error('ABSOLUTE_STATE_DIRECTORY_REQUIRED'); fs.mkdirSync(directory, { recursive: true, mode: 0o700 }); if (fs.realpathSync(directory) !== path.resolve(directory)) throw Error('STATE_DIRECTORY_SYMLINK'); this.#file = path.join(directory, 'task.json'); this.#plan = deepFreeze(copy(plan)); this.#host = host; this.#state = { schema: 'guanghu.dark-core-task/v1', plan: this.#plan, planHash: digest(this.#plan), status: 'READY', nextStep: 0, receipts: [], suggestions: [], events: [], seenHumanEvents: [], uncertainSteps: [] }; // Exclusive creation prevents two writers and implicit replay after a crash. fs.writeFileSync(this.#file, JSON.stringify(this.#state), { flag: 'wx', mode: 0o600 }); this.#record('CREATED', { goal: plan.goal }); } get snapshot() { return copy(this.#state); } #record(type, detail = {}) { const previous = this.#state.events.at(-1)?.hash ?? null; const event = { sequence: this.#state.events.length + 1, at: new Date().toISOString(), type, detail, previous }; event.hash = digest(event); this.#state.events.push(event); const temporary = this.#file + '.tmp'; fs.writeFileSync(temporary, JSON.stringify(this.#state, null, 2) + '\n', { mode: 0o600 }); fs.renameSync(temporary, this.#file); } async human(input) { const event = deepFreeze(copy(input)); if (!text(event.id) || event.taskId !== this.#plan.id || !['ADVICE', 'STOP', 'WITHDRAW', 'CORRECTION', 'CHANGE_GOAL', 'RESUME'].includes(event.kind) || !text(event.text)) throw Error('INVALID_HUMAN_EVENT'); // Only the host adapter authenticates provenance. A field saying "human" is insufficient. if (await this.#host.verifyHuman(event) !== true) throw Error('UNTRUSTED_CONTROL_SOURCE'); if (this.#state.seenHumanEvents.includes(event.id)) return this.snapshot; if (terminal.has(this.#state.status)) throw Error('TASK_CLOSED'); if (event.kind === 'RESUME') { if (this.#busy || this.#state.status !== 'PAUSED') throw Error('NOT_SETTLED_PAUSE'); if (this.#state.uncertainSteps.length) throw Error('UNCERTAIN_EFFECTS_REQUIRE_NEW_RECONCILED_TASK'); this.#pending = undefined; this.#state.status = 'READY'; } else if (event.kind === 'ADVICE') { this.#state.suggestions.push({ id: event.id, text: event.text, state: 'QUEUED' }); } else { const next = event.kind === 'WITHDRAW' ? 'WITHDRAW' : event.kind === 'STOP' ? 'STOP' : 'PAUSE'; // A later correction must never demote an already accepted stop or withdrawal. const rank = { PAUSE: 1, STOP: 2, WITHDRAW: 3 }; if (!this.#pending || rank[next] > rank[this.#pending]) this.#pending = next; this.#epoch++; this.#state.status = this.#pending + '_REQUESTED'; this.#abort?.abort(new Error(event.kind)); if (!this.#busy) this.#settleControl(); } this.#state.seenHumanEvents.push(event.id); this.#record('HUMAN_' + event.kind, { id: event.id, text: event.text }); return this.snapshot; } decideAdvice(id, decision, reason) { if (!['ACCEPT', 'DEFER', 'REJECT'].includes(decision) || !text(reason)) throw Error('REASON_REQUIRED'); const item = this.#state.suggestions.find(s => s.id === id); if (!item || item.state !== 'QUEUED' || terminal.has(this.#state.status)) throw Error('SUGGESTION_UNAVAILABLE'); Object.assign(item, { state: decision, reason }); this.#record('PERSONA_ADVICE_DECISION', { id, decision, reason }); // ACCEPT records agreement; it does not rewrite the immutable execution plan. return this.snapshot; } #settleControl() { if (this.#pending) this.#state.status = { PAUSE: 'PAUSED', STOP: 'STOPPED', WITHDRAW: 'WITHDRAWN' }[this.#pending]; } async run() { if (this.#busy || this.#state.status !== 'READY') throw Error('TASK_NOT_READY'); this.#busy = true; this.#state.status = 'RUNNING'; this.#record('RUN_STARTED'); try { while (this.#state.nextStep < this.#plan.steps.length && !this.#pending) { const step = this.#plan.steps[this.#state.nextStep]; const epoch = this.#epoch; this.#abort = new AbortController(); const signal = this.#abort.signal; const grant = await this.#host.authorize({ plan: this.#plan, step, planHash: this.#state.planHash, signal }); // Stop can arrive while the host is obtaining native approval. if (this.#pending || epoch !== this.#epoch) break; if (!grant || grant.planHash !== this.#state.planHash || grant.stepId !== step.id || !text(grant.receipt)) { throw Error('HOST_AUTHORIZATION_MISMATCH'); } this.#record('STEP_STARTED', { stepId: step.id, authorizationReceipt: grant.receipt }); let result; try { result = await this.#host.execute({ plan: this.#plan, step, grant, signal }); if (!result || typeof result !== 'object' || !text(result.kind)) throw Error('EXECUTION_RECEIPT_REQUIRED'); result = deepFreeze(copy(result)); const verified = await this.#host.verify({ plan: this.#plan, step, result }); if (verified !== true) throw Error('TARGET_READBACK_FAILED'); } catch (error) { // Abort or failure does not prove that a side effect did not happen. this.#state.uncertainSteps.push(step.id); throw error; } this.#state.receipts.push({ stepId: step.id, result: copy(result), verified: true }); this.#state.nextStep++; this.#record('STEP_VERIFIED', { stepId: step.id }); } if (!this.#pending && this.#state.nextStep === this.#plan.steps.length) this.#state.status = 'COMPLETED'; } catch (error) { this.#state.lastError = String(error.message ?? error); if (!this.#pending) this.#state.status = 'FAILED'; this.#record('EXECUTION_INTERRUPTED', { error: this.#state.lastError }); } finally { this.#busy = false; this.#abort = undefined; this.#settleControl(); this.#record('RUN_SETTLED', { status: this.#state.status }); } return this.snapshot; } } export function inspectTask(directory) { const state = JSON.parse(fs.readFileSync(path.join(directory, 'task.json'), 'utf8')); if (digest(state.plan) !== state.planHash) throw Error('PLAN_INTEGRITY_FAILURE'); let previous = null; for (let i = 0; i < state.events.length; i++) { const { hash, ...event } = state.events[i]; if (event.sequence !== i + 1 || event.previous !== previous || digest(event) !== hash) throw Error('JOURNAL_INTEGRITY_FAILURE'); previous = hash; } // Inspection never resumes potentially interrupted real operations. return { ...state, recoveryRequired: !terminal.has(state.status), recoveryPolicy: 'INSPECT_REAL_EFFECTS_AND_AUTHORIZE_NEW_TASK_NO_AUTOMATIC_REPLAY' }; }