/** Phase 0 executable contract model for XZBT 0.1 GC5. */ export const DISPATCH_BUDGET = 1024; export const TERMINATION_HOOK_LIMIT = 256; export const CLEANUP_TICKS = 5 * 60; export class ConditionTrigger { constructor(holdTicks = 0) { this.holdTicks = holdTicks; this.armed = false; this.trueTicks = 0; } evaluate(value) { if (!value) { this.armed = true; this.trueTicks = 0; return false; } if (!this.armed) return false; this.trueTicks++; if (this.trueTicks < Math.max(1, this.holdTicks)) return false; this.armed = false; this.trueTicks = 0; return true; } } export class DeferredStartQueue { constructor(defaultExpiryTicks = 5 * 60 * 60) { this.defaultExpiryTicks = defaultExpiryTicks; this.requests = new Map(); } defer({ definitionId, priority = 50, creationTick, documentOrder, expiryTicks, sampledInputs }) { if (this.requests.has(definitionId)) return false; this.requests.set(definitionId, { definitionId, priority, creationTick, documentOrder, expiresAt: creationTick + (expiryTicks ?? this.defaultExpiryTicks), sampledInputs }); return true; } dispatch(tick, isEligible) { for (const [id, request] of this.requests) { if (tick >= request.expiresAt) this.requests.delete(id); } const ordered = [...this.requests.values()].sort((left, right) => right.priority - left.priority || left.creationTick - right.creationTick || left.documentOrder - right.documentOrder ); const started = []; for (const request of ordered) { if (!isEligible(request)) continue; started.push(request); this.requests.delete(request.definitionId); } return started; } } export function drainDispatch(initialQueue, expand, budget = DISPATCH_BUDGET) { const queue = [...initialQueue]; const processed = []; let consumed = 0; while (queue.length > 0 && consumed < budget) { const unit = queue.shift(); consumed++; processed.push(unit); const added = expand(unit) || []; queue.push(...added); } return { processed, consumed, exhausted: queue.length > 0, discarded: queue, failedOwnerIds: [...new Set(queue.map((unit) => unit.ownerId).filter(Boolean))] }; } export class OwnershipModel { constructor() { this.tickIndex = 0; this.state = new Map(); this.owners = new Map(); this.resources = new Map(); this.diagnostics = []; this.nextResourceId = 1; } startScenario(id, { onStart = [], onComplete = [], onCancel = [] } = {}) { const owner = { id, status: 'STARTING', blocked: false, onComplete, onCancel, terminalCause: null }; this.owners.set(id, owner); const succeeded = this.executeActions(onStart, id, 'ordinary'); if (succeeded) owner.status = 'ACTIVE'; return owner; } executeActions(actions, ownerId, phase = 'ordinary') { const owner = this.owners.get(ownerId); for (let index = 0; index < actions.length; index++) { const action = actions[index]; if (phase === 'ordinary' && owner?.blocked) return false; try { if (phase === 'termination' && !['set', 'sound', 'fail'].includes(action.type)) { throw new Error(`Action '${action.type}' is not permitted in a termination hook.`); } if (action.type === 'set') { this.state.set(action.target, action.value); } else if (action.type === 'sound') { if (action.continuous || action.ownership === 'persistent') { throw new Error('Termination hooks permit only nonpersistent one-shot sounds.'); } this.createResource(ownerId, { kind: 'termination-sound', releaseTicks: action.releaseTicks ?? 0 }); } else if (action.type === 'resource') { this.createResource(ownerId, action); } else if (action.type === 'event') { if (!this.executeActions(action.actions || [], ownerId, phase)) return false; } else if (action.type === 'fail') { throw new Error(action.message || 'injected failure'); } } catch (error) { this.diagnostics.push({ code: 'ERR_ACTION_FAILURE', ownerId, phase, index, message: error.message }); if (phase === 'termination') continue; if (action.critical) { this.terminate(ownerId, 'failure'); return false; } } } return true; } createResource(ownerId, { kind = 'generic', releaseTicks = 0, ownership = 'scenario' } = {}) { const effectiveOwner = ownership === 'persistent' ? 'performance' : ownerId; const id = `resource-${this.nextResourceId++}`; this.resources.set(id, { id, kind, ownerId: effectiveOwner, releaseTicks, cleanupAt: null }); return id; } terminate(ownerId, cause) { const owner = this.owners.get(ownerId); if (!owner || owner.blocked) return; owner.blocked = true; owner.terminalCause = cause; const hook = cause === 'complete' ? owner.onComplete : owner.onCancel; this.executeActions(hook.slice(0, TERMINATION_HOOK_LIMIT), ownerId, 'termination'); if (hook.length > TERMINATION_HOOK_LIMIT) { this.diagnostics.push({ code: 'ERR_DISPATCH_BUDGET', ownerId, phase: 'termination' }); } for (const resource of this.resources.values()) { if (resource.ownerId !== ownerId) continue; const boundedRelease = Math.min(resource.releaseTicks, CLEANUP_TICKS); if (boundedRelease === 0) this.resources.delete(resource.id); else { resource.ownerId = `cleanup:${ownerId}`; resource.cleanupAt = this.tickIndex + boundedRelease; resource.forceAt = this.tickIndex + CLEANUP_TICKS; resource.forceRequired = resource.releaseTicks > CLEANUP_TICKS; } } owner.status = cause === 'complete' ? 'COMPLETED' : cause === 'failure' ? 'FAILED' : 'CANCELLED'; } tick(count = 1) { for (let step = 0; step < count; step++) { this.tickIndex++; for (const resource of [...this.resources.values()]) { if (!resource.cleanupAt || this.tickIndex < resource.cleanupAt) continue; if (resource.forceRequired && this.tickIndex >= resource.forceAt) { this.diagnostics.push({ code: 'WARN_CLEANUP_FORCED', resourceId: resource.id }); } this.resources.delete(resource.id); } } } ownedResourceCount(ownerId) { return [...this.resources.values()].filter((resource) => resource.ownerId === ownerId).length; } }