/* fiber — component runtime fiber scheduling with cooperative deadline tracking. Do not render — fibers schedule work, they do not produce output. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. */ import type { ResolvedComponentIdentityV1 } from "@warpgogol/werkstatt-shared/component"; import { LifecycleMachine, type ComponentLifecycleState } from "./lifecycle.ts"; import type { EffectUnwindReportV1, EffectUnwindEntryV1, EffectHandler } from "./effects.ts"; export interface Deadline { readonly timeoutMs: number; readonly signal?: AbortSignal; } export interface QuiescenceResult { readonly drained: boolean; readonly timedOut: boolean; readonly remainingOperations: number; readonly durationMs: number; } export interface OwnedOperation { readonly id: string; readonly execute: (signal: AbortSignal) => Promise; readonly cancellationPoint?: () => boolean; } interface RunningOperation { id: string; controller: AbortController; promise: Promise; startedAt: number; } export class ComponentFiber { readonly component: ResolvedComponentIdentityV1; private readonly lifecycle: LifecycleMachine; private readonly operations: Map = new Map(); private readonly effects: Map = new Map(); private readonly children: ComponentFiber[] = []; private disposed = false; constructor(component: ResolvedComponentIdentityV1, initial?: ComponentLifecycleState) { this.component = component; this.lifecycle = new LifecycleMachine(component, initial); } get state(): ComponentLifecycleState { return this.lifecycle.state; } get isTerminal(): boolean { return this.lifecycle.isTerminal; } get canAcceptNewCalls(): boolean { return this.lifecycle.canAcceptNewCalls; } get activeOperationCount(): number { return this.operations.size; } transitionTo(target: ComponentLifecycleState) { return this.lifecycle.transitionTo(target); } registerEffect(id: string, handler: EffectHandler): void { if (this.disposed) { throw new Error( `FIBER-01: cannot register effect on disposed fiber for ${this.component.componentId}`, ); } this.effects.set(id, handler); } addChild(child: ComponentFiber): void { if (this.disposed) { throw new Error( `FIBER-02: cannot add child to disposed fiber for ${this.component.componentId}`, ); } this.children.push(child); } async run(operation: OwnedOperation): Promise { if (!this.lifecycle.canAcceptNewCalls) { throw new Error( `FIBER-03: fiber ${this.component.componentId} cannot accept new calls in state ${this.lifecycle.state}`, ); } const controller = new AbortController(); const startedAt = Date.now(); const promise = operation.execute(controller.signal); const entry: RunningOperation = { id: operation.id, controller, promise, startedAt }; this.operations.set(operation.id, entry); try { const result = await promise; this.operations.delete(operation.id); return result as T; } catch (e) { this.operations.delete(operation.id); throw e; } } async drain(deadline: Deadline): Promise { const start = Date.now(); const drainResult = this.lifecycle.transitionTo("draining"); if (!drainResult.ok) { return { drained: this.operations.size === 0, timedOut: false, remainingOperations: this.operations.size, durationMs: 0, }; } const deadlineMs = start + deadline.timeoutMs; while (this.operations.size > 0) { const now = Date.now(); if (now >= deadlineMs) { return { drained: false, timedOut: true, remainingOperations: this.operations.size, durationMs: now - start, }; } if (deadline.signal?.aborted) { return { drained: false, timedOut: true, remainingOperations: this.operations.size, durationMs: now - start, }; } const ops = Array.from(this.operations.values()); const sleepMs = Math.min(10, deadlineMs - now); await Promise.race([ Promise.allSettled(ops.map((o) => o.promise)), new Promise((resolve) => setTimeout(resolve, sleepMs)), ]); } return { drained: true, timedOut: false, remainingOperations: 0, durationMs: Date.now() - start, }; } cancelAllOperations(): void { if (!this.lifecycle.isDraining && this.lifecycle.state !== "active") { return; } for (const op of this.operations.values()) { op.controller.abort(); } } async dispose(): Promise { if (this.disposed) { return { entries: [], allSucceeded: true, quarantined: false }; } if (this.lifecycle.state === "active") { this.lifecycle.transitionTo("draining"); } if (this.lifecycle.state === "draining") { this.lifecycle.transitionTo("unloading"); } this.cancelAllOperations(); for (const child of this.children) { await child.dispose(); } const entries: EffectUnwindEntryV1[] = []; const effectIds = Array.from(this.effects.keys()).reverse(); for (const id of effectIds) { const handler = this.effects.get(id)!; try { const result = await handler.dispose(); entries.push({ componentId: this.component.componentId, effectClass: handler.effectClass, outcome: result.outcome, error: result.error, }); } catch (e) { entries.push({ componentId: this.component.componentId, effectClass: handler.effectClass, outcome: "failed-rollback", error: e instanceof Error ? e.message : String(e), }); } } this.effects.clear(); this.disposed = true; const report: EffectUnwindReportV1 = { entries, allSucceeded: entries.every( (e) => e.outcome === "aborted" || e.outcome === "compensated" || e.outcome === "withheld" || e.outcome === "committed", ) && !entries.some((e) => e.outcome === "quarantined" || e.outcome === "failed-rollback"), quarantined: entries.some( (e) => e.outcome === "quarantined" || e.outcome === "failed-rollback", ), }; if (report.quarantined) { this.lifecycle.transitionTo("quarantined"); } else { this.lifecycle.transitionTo("disposed"); } return report; } }