forgeos 0.1.0-alpha.71 → 0.1.0-alpha.72

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.
Files changed (38) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +6 -0
  3. package/docs/agent-fabric-programs.md +34 -2
  4. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_COMPETITIVE_EVIDENCE.md +75 -0
  5. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_COMPETITIVE_PLAN.md +137 -0
  6. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_COMPETITIVE_REVIEW.md +56 -0
  7. package/examples/agent-fabric-v2/templates/README.md +46 -0
  8. package/examples/agent-fabric-v2/templates/bugfix.json +12 -0
  9. package/examples/agent-fabric-v2/templates/migration.json +13 -0
  10. package/examples/agent-fabric-v2/templates/review.json +12 -0
  11. package/package.json +5 -1
  12. package/scripts/summarize-fabric-benchmark.mjs +6 -0
  13. package/src/forge/_generated/releaseManifest.json +1 -1
  14. package/src/forge/_generated/releaseManifest.ts +3 -3
  15. package/src/forge/agent-fabric/claude-program-worker.ts +95 -0
  16. package/src/forge/agent-fabric/codex-sdk-worker.ts +10 -4
  17. package/src/forge/agent-fabric/index.ts +5 -0
  18. package/src/forge/agent-fabric/local-task-server.ts +2 -1
  19. package/src/forge/agent-fabric/managed-environment.ts +14 -8
  20. package/src/forge/agent-fabric/managed-workspace.ts +4 -0
  21. package/src/forge/agent-fabric/program-api.ts +1 -1
  22. package/src/forge/agent-fabric/program-authoring.ts +48 -0
  23. package/src/forge/agent-fabric/program-benchmark.ts +76 -0
  24. package/src/forge/agent-fabric/program-capabilities.ts +14 -0
  25. package/src/forge/agent-fabric/program-contract.ts +19 -3
  26. package/src/forge/agent-fabric/program-observation.ts +63 -0
  27. package/src/forge/agent-fabric/program-runtime-options.ts +23 -0
  28. package/src/forge/agent-fabric/program-scheduler.ts +25 -6
  29. package/src/forge/agent-fabric/program-service.ts +172 -35
  30. package/src/forge/agent-fabric/program-store.ts +9 -1
  31. package/src/forge/agent-fabric/program-templates.ts +96 -0
  32. package/src/forge/agent-fabric/program-view.ts +15 -1
  33. package/src/forge/agent-fabric/program-worker.ts +73 -20
  34. package/src/forge/agent-memory/mcp.ts +8 -2
  35. package/src/forge/cli/fabric.ts +4 -2
  36. package/src/forge/cli/main.ts +2 -0
  37. package/src/forge/cli/parse.ts +3 -1
  38. package/src/forge/version.ts +1 -1
@@ -1,4 +1,7 @@
1
- import { programVisualization } from "./program-view.ts";
1
+ import { proposeWorkflowTemplate, generateRegistryAuthorTypes } from "./program-authoring.ts";
2
+ import type { ProgramTemplateKind, ProgramTemplateOptions } from "./program-templates.ts";
3
+ import { recordProgramObservation, summarizeProgramUsage, programResourceLiability, type ProgramWorkerObservation } from "./program-observation.ts";
4
+ import { programVisualization, programDiagnostics } from "./program-view.ts";
2
5
  import { programBlock, programGraph, canonicalItemKey, compareProgramKeys, programSegment, type ProgramBlock } from "./program-structure.ts";
3
6
  import { ProgramActivityScheduler, type ActivityScope } from "./program-scheduler.ts";
4
7
  import { ProgramOwnerLease } from "./program-owner.ts";
@@ -15,8 +18,9 @@ import { programAssert, programDigest, programValueBytes, programWithin, program
15
18
  type WorkflowProgramV2, type ProgramRegistry, type ProgramOperation, type ProgramExpr, type ProgramRef,
16
19
  type ProgramValue, type ProgramRunV2, type ProgramCandidate, type ProgramAssessment, type ProgramAssessmentContext, type ProgramRepairResult } from "./program-contract.ts";
17
20
 
18
- export const PROGRAM_RUN_ACTIONS = ["program-validate", "program-start", "program-status", "program-wait", "program-pause", "program-resume", "program-signal", "program-replan", "program-cancel", "program-reconcile", "program-apply", "program-history", "program-artifact-get", "program-explain", "program-diff"] as const;
21
+ export const PROGRAM_RUN_ACTIONS = ["program-author", "program-author-types", "program-validate", "program-start", "program-status", "program-wait", "program-pause", "program-resume", "program-signal", "program-replan", "program-cancel", "program-reconcile", "program-apply", "program-history", "program-artifact-get", "program-explain", "program-diff"] as const;
19
22
  export type ProgramRunAction = typeof PROGRAM_RUN_ACTIONS[number];
23
+ export interface ProgramRuntimeOptions { ownerCapacity?: number; ownerMaxTokens?: number }
20
24
  interface Context { prefix: string; steps: ProgramOperation[]; program: WorkflowProgramV2; inputRef?: string; parent?: Context; item?: unknown; itemKey?: string; state?: unknown; depth: number; chain: Set<string>; dependencies?: Set<string>; activityScopes?: ActivityScope[]; signal?: AbortSignal }
21
25
  interface ActivityOutput { data: unknown; candidate: ProgramCandidate; attemptId: string; evidenceRefs?: string[] }
22
26
  const asRecord = (value: unknown): Record<string, unknown> => { programAssert(value && typeof value === "object" && !Array.isArray(value), "Object required"); return value as Record<string, unknown>; };
@@ -30,13 +34,16 @@ export class ProgramRunService {
30
34
  private controllers = new Map<string, AbortController>();
31
35
  private attemptControllers = new Map<string, AbortController>();
32
36
  private jobs = new Map<string, Promise<unknown>>();
33
- private scheduler = new ProgramActivityScheduler(4);
37
+ private scheduler: ProgramActivityScheduler;
38
+ private admissionTail: Promise<unknown> = Promise.resolve();
34
39
  private reservations = new Map<string, () => void>();
35
40
  private timers = new Map<string, ReturnType<typeof setTimeout>>();
36
41
  private wakeRequests = new Set<string>();
37
42
  private closed = false;
38
- private constructor(readonly store: ProgramRunStore, private registry: ProgramRegistry, private adapter: ProgramWorkerAdapter, private owner: ProgramOwnerLease, private registryFile: boolean) {}
39
- static async open(root: string, registry?: ProgramRegistry, adapter: ProgramWorkerAdapter = prepareProgramWorker): Promise<ProgramRunService> {
43
+ private constructor(readonly store: ProgramRunStore, private registry: ProgramRegistry, private adapter: ProgramWorkerAdapter, private owner: ProgramOwnerLease, private registryFile: boolean, private runtime: ProgramRuntimeOptions) { this.scheduler = new ProgramActivityScheduler(runtime.ownerCapacity ?? 4); }
44
+ static async open(root: string, registry?: ProgramRegistry, adapter: ProgramWorkerAdapter = prepareProgramWorker, runtime: ProgramRuntimeOptions = {}): Promise<ProgramRunService> {
45
+ programAssert(runtime.ownerCapacity === undefined || Number.isSafeInteger(runtime.ownerCapacity) && runtime.ownerCapacity > 0 && runtime.ownerCapacity <= 32, "Invalid owner capacity");
46
+ programAssert(runtime.ownerMaxTokens === undefined || Number.isSafeInteger(runtime.ownerMaxTokens) && runtime.ownerMaxTokens > 0, "Invalid owner token budget");
40
47
  const store = await ProgramRunStore.open(root);
41
48
  const registryFile = !registry;
42
49
  if (!registry) {
@@ -44,7 +51,7 @@ export class ProgramRunService {
44
51
  catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; registry = { schemas: {}, executors: {}, policies: {}, acceptance: {}, populations: {} }; }
45
52
  }
46
53
  const owner = await ProgramOwnerLease.acquire(store.directory);
47
- const service = new ProgramRunService(store, registry, adapter, owner, registryFile);
54
+ const service = new ProgramRunService(store, registry, adapter, owner, registryFile, runtime);
48
55
  try {
49
56
  for (const id of await store.list()) {
50
57
  const run = (await store.read(id))!;
@@ -65,6 +72,29 @@ export class ProgramRunService {
65
72
  } catch (error) { await owner.close(); throw error; }
66
73
  }
67
74
  async close(): Promise<void> { if (this.closed) return; this.closed = true; for (const timer of this.timers.values()) clearTimeout(timer); for (const controller of this.controllers.values()) controller.abort(); await Promise.allSettled([...this.active.values(), ...this.applications.values()]); await this.owner.close(); }
75
+ /** Trusted host API: resizing changes future admission, never cancels admitted work. */
76
+ async updateRuntimeOptions(options: ProgramRuntimeOptions): Promise<void> {
77
+ await this.owner.assert(); programAssert(!this.closed, "Owner is closed");
78
+ const next = { ...this.runtime, ...options };
79
+ programAssert(next.ownerCapacity === undefined || Number.isSafeInteger(next.ownerCapacity) && next.ownerCapacity > 0 && next.ownerCapacity <= 32, "Invalid owner capacity");
80
+ programAssert(next.ownerMaxTokens === undefined || Number.isSafeInteger(next.ownerMaxTokens) && next.ownerMaxTokens > 0, "Invalid owner token budget");
81
+ await this.admit(async () => { this.runtime = next; this.scheduler.setOwnerCapacity(next.ownerCapacity ?? 4); });
82
+ }
83
+ private async resourceSummary(): Promise<{ ownerMaxTokens: number | null; liabilityTokens: number; finalReservedTokens: number; runs: { runId: string; tokens: number; finalReservedTokens: number; unknownAttempts: number }[] }> {
84
+ const runs = [];
85
+ for (const runId of await this.store.list()) {
86
+ const state = await this.state(runId), attempts = Object.values(state.attempts);
87
+ const finalFloor = registryEntry(state.registry.policies, state.program.policy).resources?.finalReserveTokens ?? 0;
88
+ const finalSpent = attempts.filter(attempt => attempt.resourceReservation?.phase === "final").reduce((sum, attempt) => sum + programResourceLiability(attempt), 0);
89
+ runs.push({ runId, tokens: attempts.reduce((sum, attempt) => sum + programResourceLiability(attempt), 0), finalReservedTokens: ["completed", "acceptance-ready", "applied", "canceled"].includes(state.status) ? 0 : Math.max(0, finalFloor - finalSpent), unknownAttempts: attempts.filter(attempt => attempt.resourceReservation?.status !== "held" && summarizeProgramUsage(attempt).certainty === "unknown").length });
90
+ }
91
+ return { ownerMaxTokens: this.runtime.ownerMaxTokens ?? null, liabilityTokens: runs.reduce((sum, run) => sum + run.tokens, 0), finalReservedTokens: runs.reduce((sum, run) => sum + run.finalReservedTokens, 0), runs };
92
+ }
93
+ private async admit<T>(action: () => Promise<T>): Promise<T> {
94
+ const task = this.admissionTail.then(action);
95
+ this.admissionTail = task.catch(() => {});
96
+ return task;
97
+ }
68
98
  private async authorize(run: ProgramRunV2, executorRef?: ProgramRef): Promise<void> {
69
99
  await this.owner.assert(); programAssert(!this.closed, "Owner is closed");
70
100
  const live = this.registryFile ? JSON.parse(await readFile(join(this.store.root, ".forge/fabric-programs.json"), "utf8")) as ProgramRegistry : this.registry;
@@ -124,9 +154,17 @@ export class ProgramRunService {
124
154
  } finally { context.signal?.removeEventListener("abort", abort); }
125
155
  }
126
156
  async execute(action: ProgramRunAction, request: Record<string, unknown>): Promise<unknown> {
157
+ programAssert(!Object.hasOwn(request, "registry"), "Registry is owner-controlled project configuration, never request data");
158
+ if (action === "program-author" || action === "program-author-types") {
159
+ await this.owner.assert(); programAssert(!this.closed, "Owner is closed");
160
+ const registry = this.registryFile ? JSON.parse(await readFile(join(this.store.root, ".forge/fabric-programs.json"), "utf8")) as ProgramRegistry : this.registry;
161
+ if (action === "program-author-types") return { source: generateRegistryAuthorTypes(registry), registryDigest: programDigest(registry) };
162
+ programAssert(["review", "bugfix", "migration"].includes(String(request.kind)), "Unknown workflow template");
163
+ programValueBytes(request.options, 40000, 128);
164
+ return proposeWorkflowTemplate(request.kind as ProgramTemplateKind, asRecord(request.options) as unknown as ProgramTemplateOptions, registry);
165
+ }
127
166
  await this.owner.assert();
128
167
  programAssert((PROGRAM_RUN_ACTIONS as readonly string[]).includes(action), "Unsupported program action");
129
- programAssert(!Object.hasOwn(request, "registry"), "Registry is owner-controlled project configuration, never request data");
130
168
  if (action === "program-validate" || action === "program-start") {
131
169
  const program = request.source ? lowerWorkflowSource(String(request.source)) : request.program as WorkflowProgramV2;
132
170
  validateWorkflowProgram(program, this.registry);
@@ -156,16 +194,25 @@ export class ProgramRunService {
156
194
  }
157
195
  const inputRef = await this.store.put(request.input, policy.maxOutputBytes), baseRef = base ? await this.store.put(base) : undefined;
158
196
  const now = new Date();
159
- const run = await this.store.transact(runId, "start", () => ({ schemaVersion: 2, runId, requestId, requestDigest: fingerprint,
197
+ const run = await this.admit(async () => {
198
+ const duplicate = await this.store.read(runId);
199
+ if (duplicate) { programAssert(duplicate.requestDigest === fingerprint, "Start requestId changed", "AF_PROGRAM_CONFLICT"); return duplicate; }
200
+ if (this.runtime.ownerMaxTokens !== undefined) {
201
+ programAssert(policy.resources, "Owner budget requires per-run reservation policy", "AF_PROGRAM_BUDGET");
202
+ const resources = await this.resourceSummary();
203
+ programAssert(resources.liabilityTokens + resources.finalReservedTokens + (policy.resources.finalReserveTokens ?? 0) <= this.runtime.ownerMaxTokens, "Owner cannot reserve final evaluation budget", "AF_PROGRAM_BUDGET");
204
+ }
205
+ return this.store.transact(runId, "start", () => ({ schemaVersion: 2, runId, requestId, requestDigest: fingerprint,
160
206
  program, registry, programDigest: programDigest(program), registryDigest: programDigest(registry),
161
207
  inputRef, baselineDigest, ...(baseRef ? { baseRef } : {}), createdAt: now.toISOString(), deadlineAt: new Date(now.getTime() + policy.deadlineMs).toISOString(),
162
208
  version: 0, semanticVersion: 1, revision: 1, planRevision: 1, status: "executing", operations: {}, attempts: {}, seals: {}, signals: [], candidates: {}, assessments: {}, coverageReceipts: {}, deltas: {}, waits: {}, repairs: {}, collections: {}, totalAttempts: 0 }), { requestId, fingerprint, expectedVersion: 0 });
209
+ });
163
210
  this.schedule(runId); return run;
164
211
  }
165
212
  const runId = idText(request.runId);
166
213
  if (action === "program-status") return this.state(runId);
167
214
  if (action === "program-history") return this.store.history(runId);
168
- if (action === "program-explain") { const state = await this.state(runId); return { runId, graph: programVisualization(state.program, state.operations), status: state.status, reason: state.reason ?? null, operations: state.operations, scopes: state.scopeResults ?? {}, queue: state.queue ?? [], waits: state.waits, assessments: state.assessments, scheduler: this.scheduler.snapshot(), storage: this.store.metrics, guarantees: { attemptsUnit: "owner-visible-dispatch", providerCalls: "adapter-dependent", crossRunCache: false } }; }
215
+ if (action === "program-explain") { const state = await this.state(runId); return { runId, graph: programVisualization(state.program, state.operations), diagnostics: programDiagnostics(state), status: state.status, reason: state.reason ?? null, operations: state.operations, scopes: state.scopeResults ?? {}, queue: state.queue ?? [], waits: state.waits, assessments: state.assessments, scheduler: this.scheduler.snapshot(), storage: this.store.metrics, resources: await this.resourceSummary(), guarantees: { attemptsUnit: "owner-visible-dispatch", providerCalls: "adapter-dependent", crossRunCache: false, resourceEnforcement: "admission-only", ownerTokenLimit: this.runtime.ownerMaxTokens ?? null } }; }
169
216
  if (action === "program-artifact-get") {
170
217
  const state = await this.state(runId), ref = String(request.ref); programAssert(/^sha256:[a-f0-9]{64}$/.test(ref) && JSON.stringify(state).includes(ref), "Artifact reference is not reachable from this run");
171
218
  if (request.binary === true) { programAssert(Object.values(state.artifactRecords ?? {}).some(record => record.artifactRef === ref), "Binary artifact not registered for this run"); return { ref, base64: (await this.store.getBinary(ref, Math.min(8 * 1024 * 1024, Number(request.maxBytes ?? 8 * 1024 * 1024)))).toString("base64") }; } return this.store.get(ref);
@@ -188,7 +235,7 @@ export class ProgramRunService {
188
235
  if (action === "program-reconcile" && (request.publication === true || request.publication === "retry")) return this.reconcileApply(runId, request);
189
236
  const state = await this.store.transact(runId, action, current => {
190
237
  programAssert(current, "Run not found");
191
- programAssert(current.status !== "applied" && (current.status !== "canceled" || action === "program-reconcile"), "Terminal runs require an explicit successor");
238
+ programAssert((current.status !== "applied" || action === "program-reconcile" && request.resolution === "usage") && (current.status !== "canceled" || action === "program-reconcile"), "Terminal runs require an explicit successor");
192
239
  if (current.status === "applying" || current.status === "apply-uncertain") {
193
240
  programAssert(action === "program-signal" || action === "program-cancel", "Apply intent frozen; reconcile before changing execution");
194
241
  }
@@ -236,13 +283,26 @@ export class ProgramRunService {
236
283
  current.program = program; current.programDigest = programDigest(program); current.planRevision++; current.semanticVersion++;
237
284
  delete current.gateRef; delete current.acceptedCandidate; delete current.resultRef;
238
285
  } else if (action === "program-reconcile") {
239
- programAssert(request.resolution === "failed" && typeof request.reason === "string" && request.reason.length > 0, "Reconciliation requires observed failure/effect evidence");
286
+ programAssert(["failed", "usage"].includes(String(request.resolution)) && typeof request.reason === "string" && request.reason.length > 0, "Reconciliation requires observed failure/effect evidence");
287
+ if (request.resolution === "usage") {
288
+ const attempt = current.attempts[idText(request.attemptId)];
289
+ programAssert(attempt && attempt.outcome !== "running" && summarizeProgramUsage(attempt).certainty === "unknown" && typeof request.authorization === "string" && request.authorization.length > 0, "Usage reconciliation requires unknown consumption, stopped attempt and authorized evidence");
290
+ const usage = asRecord(request.usage) as unknown as NonNullable<ProgramWorkerObservation["usage"]>;
291
+ programAssert(Number.isSafeInteger(usage.input_tokens) && Number.isSafeInteger(usage.output_tokens), "Observed usage counters required");
292
+ recordProgramObservation(attempt, { id: `reconcile:${idText(request.requestId)}`, type: "usage.reconciled", source: "owner-evidence", at: new Date().toISOString(), usage, metadata: { reason: String(request.reason), authorization: request.authorization } });
293
+ return current;
294
+ }
240
295
  if (request.operationId) {
241
296
  const operation = current.operations[idText(request.operationId)]; programAssert(operation?.status === "uncertain" && !Object.values(current.attempts).some(attempt => (attempt.operationId === operation.id || attempt.operationId.startsWith(`${operation.id}/`)) && (attempt.outcome === "running" || attempt.outcome === "uncertain")), "Observe/reconcile worker attempts first");
242
297
  operation.status = operation.retired ? "skipped" : "needs-attention"; operation.reason = String(request.reason); return current;
243
298
  }
244
299
  const attempt = current.attempts[idText(request.attemptId)]; programAssert(attempt?.outcome === "uncertain", "Only uncertain attempts can be reconciled");
245
- attempt.outcome = "infrastructure_failed"; attempt.reason = String(request.reason); if (attempt.reservation) attempt.reservation.status = "released";
300
+ if (request.resumeThread === true) {
301
+ const observation = asRecord(request.observation);
302
+ programAssert(observation.terminated === true && typeof observation.workspaceDigest === "string" && /^sha256:[a-f0-9]{64}$/.test(observation.workspaceDigest) && attempt.threadId && attempt.recovery && current.operations[attempt.operationId]?.generation === attempt.generation && !current.operations[attempt.operationId]?.retired, "Resume requires observed termination, matching generation and workspace identity");
303
+ attempt.recovery = { ...attempt.recovery, threadId: attempt.threadId, workspaceDigest: observation.workspaceDigest, terminationObserved: true, generationCompatible: true };
304
+ }
305
+ attempt.outcome = "infrastructure_failed"; attempt.reason = String(request.reason); if (attempt.resourceReservation) attempt.resourceReservation.status = "released"; if (attempt.reservation) attempt.reservation.status = "released";
246
306
  const operation = current.operations[attempt.operationId]; if (operation) { operation.status = operation.retired ? "skipped" : "needs-attention"; operation.reason = String(request.reason); }
247
307
  }
248
308
  return current;
@@ -251,7 +311,7 @@ export class ProgramRunService {
251
311
  if (action === "program-replan" && request.mode === "fenced") for (const attempt of Object.values(state.attempts)) if (attempt.outcome === "uncertain") this.attemptControllers.get(attempt.attemptId)?.abort();
252
312
  if (action === "program-reconcile") for (const attempt of Object.values(state.attempts)) if (attempt.reservation?.status === "released") { this.reservations.get(attempt.attemptId)?.(); this.reservations.delete(attempt.attemptId); }
253
313
  if (action === "program-resume") this.schedule(runId);
254
- if (action === "program-signal" && ["waiting", "executing"].includes(state.status)) { await this.store.transact(runId, "signal-wakeup", current => { if (current!.status === "waiting") current!.status = "executing"; return current!; }); this.schedule(runId); }
314
+ if (action === "program-signal" && ["waiting", "executing"].includes(state.status)) { await this.store.transact(runId, "signal-wakeup", current => { if (current!.status === "waiting") { current!.status = "executing"; delete current!.reason; } return current!; }); this.schedule(runId); }
255
315
  return state;
256
316
  }
257
317
  private async run(runId: string): Promise<void> {
@@ -280,7 +340,7 @@ export class ProgramRunService {
280
340
  if (data?.status === "accepted") programAssert(final.gateRef && final.acceptedCandidate, "Accepted result requires current owner gate");
281
341
  if (final.gateRef && (data?.candidate || data?.acceptedCandidate)) programAssert(this.resolveCandidate(final, data.candidate ?? data.acceptedCandidate).digest === final.acceptedCandidate?.digest, "Result candidate differs from the owner acceptance gate");
282
342
  const resultRef = await this.store.put(result, registryEntry(state.registry.policies, state.program.policy).maxOutputBytes);
283
- await this.store.transact(runId, "program-result", current => { programAssert(current?.status === "executing" && current.programDigest === state.programDigest && current.semanticVersion === state.semanticVersion, "Execution paused, canceled or revised"); current.resultRef = resultRef; current.status = current.gateRef ? "acceptance-ready" : "completed"; return current; });
343
+ await this.store.transact(runId, "program-result", current => { programAssert(current?.status === "executing" && current.programDigest === state.programDigest && current.semanticVersion === state.semanticVersion, "Execution paused, canceled or revised"); current.resultRef = resultRef; current.status = current.gateRef ? "acceptance-ready" : "completed"; delete current.reason; return current; });
284
344
  } catch (error) {
285
345
  await this.store.transact(runId, "execution-stopped", current => {
286
346
  const state = current!;
@@ -418,7 +478,7 @@ export class ProgramRunService {
418
478
  const run = await this.state(runId); this.resolveCandidate(run, candidate);
419
479
  const artifacts: ManagedArtifact[] = []; for (const ref of candidate.deltas) { const delta = run.deltas[ref]; programAssert(delta, "Delta does not belong to this run"); artifacts.push(await this.store.get<ManagedArtifact>(delta.artifactRef)); } return artifacts;
420
480
  }
421
- private async activity(runId: string, id: string, ref: ProgramRef, data: unknown, candidate: ProgramCandidate, requestedScope: string[], context?: Context): Promise<ActivityOutput> {
481
+ private async activity(runId: string, id: string, ref: ProgramRef, data: unknown, candidate: ProgramCandidate, requestedScope: string[], context?: Context, finalAssessment = false): Promise<ActivityOutput> {
422
482
  const run = await this.checkRunning(runId), executor = registryEntry(run.registry.executors, ref), policy = registryEntry(run.registry.policies, run.program.policy), acceptance = registryEntry(run.registry.acceptance, run.program.acceptance);
423
483
  if (executor.inputSchema) validateProgramData(data, registryEntry(run.registry.schemas, executor.inputSchema));
424
484
  candidate = this.resolveCandidate(run, candidate);
@@ -445,6 +505,13 @@ export class ProgramRunService {
445
505
  finally { clearTimeout(admissionDeadline); parentSignal.removeEventListener("abort", abortAdmission); }
446
506
  this.reservations.set(attemptId, release);
447
507
  let completionObserved = false;
508
+ const pendingObservations: ProgramWorkerObservation[] = [];
509
+ let dispatched = false;
510
+ const onObservation = async (observation: ProgramWorkerObservation) => {
511
+ if (!dispatched) { programAssert(pendingObservations.length < 1024, "Preparation observation budget exceeded"); pendingObservations.push(observation); return; }
512
+ await this.owner.assert();
513
+ await this.store.transact(runId, "worker-observation", current => { const attempt = current!.attempts[attemptId]; programAssert(attempt?.outcome === "running" && attempt.reservation?.ownerEpoch === this.owner.epoch && attempt.generation === current!.operations[id].generation, "Fenced worker observation"); recordProgramObservation(attempt, observation); return current!; });
514
+ };
448
515
  let timeout: ReturnType<typeof setTimeout> | undefined, removeAbort: (() => void) | undefined;
449
516
  try {
450
517
  await this.checkRunning(runId);
@@ -455,8 +522,9 @@ export class ProgramRunService {
455
522
  const base = run.baseRef ? await this.store.get<ManagedBase>(run.baseRef) : undefined;
456
523
  const evidence = [];
457
524
  for (const ref of (data as { assessmentContext?: ProgramAssessmentContext })?.assessmentContext?.evidenceRefs ?? []) { const capture = run.artifactRecords?.[ref]; programAssert(capture && capture.candidateDigest === candidate.digest, "Evidence ref outside current candidate"); evidence.push({ receiptId: ref, bytes: await this.store.getBinary(capture.artifactRef), mime: capture.mime, itemKey: capture.itemKey }); }
458
- const preparation = await this.cancellable(this.adapter({ executor, data, candidate, artifacts: await this.artifacts(runId, candidate), base, attemptId, scope, signal: controller.signal, registry: run.registry, evidence,
459
- onThread: async threadId => { await this.owner.assert(); await this.store.transact(runId, "worker-thread", current => { const attempt = current!.attempts[attemptId]; programAssert(attempt?.outcome === "running" && attempt.reservation?.ownerEpoch === this.owner.epoch, "Fenced worker callback"); attempt.threadId = threadId; return current!; }); } }), controller.signal, () => { throw new ProgramError("AF_PROGRAM_STOPPED", "Preparation canceled before dispatch"); });
525
+ const recovery = (() => { const prior = [...(completed?.attempts ?? [])].reverse().map(attempt => run.attempts[attempt]).find(attempt => attempt?.recovery?.terminationObserved && attempt.recovery.generationCompatible && attempt.inputDigest === inputDigest && attempt.executorDigest === programDigest(executor) && attempt.generation === completed?.generation); return prior?.recovery as Parameters<ProgramWorkerAdapter>[0]["recovery"]; })();
526
+ const preparation = await this.cancellable(this.adapter({ executor, data, candidate, artifacts: await this.artifacts(runId, candidate), base, attemptId, scope, signal: controller.signal, registry: run.registry, evidence, onObservation, recovery,
527
+ onThread: async threadId => { await this.owner.assert(); await this.store.transact(runId, "worker-thread", current => { const attempt = current!.attempts[attemptId]; programAssert(attempt?.outcome === "running" && attempt.reservation?.ownerEpoch === this.owner.epoch && attempt.generation === current!.operations[id].generation, "Fenced worker callback"); attempt.threadId = threadId; return current!; }); } }), controller.signal, () => { throw new ProgramError("AF_PROGRAM_STOPPED", "Preparation canceled before dispatch"); });
460
528
 
461
529
  await this.store.transact(runId, "activity-materialize", current => {
462
530
  programAssert(current?.status === "executing" && !controller.signal.aborted, "Preparation stopped before dispatch");
@@ -465,14 +533,31 @@ export class ProgramRunService {
465
533
  current!.operations[id] = { id, kind: old?.kind ?? "activity", semanticDigest: programDigest(executor), inputDigest, generation: Math.max(old?.generation ?? 0, parentGeneration), status: "running", attempts: old?.attempts ?? [], ...(old?.dependencies ? { dependencies: old.dependencies } : {}) }; return current!;
466
534
  });
467
535
  await this.authorize(run, ref);
536
+ await this.admit(async () => {
537
+ const currentState = await this.state(runId), resourcePolicy = policy.resources;
538
+ programAssert(this.runtime.ownerMaxTokens === undefined || resourcePolicy, "Owner budget requires per-run reservation policy", "AF_PROGRAM_BUDGET");
539
+ const reservationTokens = executor.tokenAccounting === "none" ? 0 : resourcePolicy?.reserveTokensPerAttempt ?? 0;
540
+ const ownAttempts = Object.values(currentState.attempts);
541
+ const spent = ownAttempts.reduce((sum, attempt) => sum + programResourceLiability(attempt), 0);
542
+ if (resourcePolicy) {
543
+ programAssert(resourcePolicy.unknownUsage === "allow" || !ownAttempts.some(attempt => attempt.resourceReservation?.status !== "held" && summarizeProgramUsage(attempt).certainty === "unknown"), "Unknown usage requires explicit policy allowance", "AF_PROGRAM_BUDGET");
544
+ const finalSpent = ownAttempts.filter(attempt => attempt.resourceReservation?.phase === "final").reduce((sum, attempt) => sum + programResourceLiability(attempt), 0);
545
+ const finalReserve = Math.max(0, (resourcePolicy.finalReserveTokens ?? 0) - finalSpent - (finalAssessment ? reservationTokens : 0));
546
+ programAssert(spent + reservationTokens + finalReserve <= resourcePolicy.maxTokens, "Run admission token budget exhausted", "AF_PROGRAM_BUDGET");
547
+ }
548
+ if (this.runtime.ownerMaxTokens !== undefined) { const ownerResources = await this.resourceSummary(); programAssert(resourcePolicy?.unknownUsage === "allow" || !ownerResources.runs.some(run => run.unknownAttempts > 0), "Owner consumption includes unknown attempts", "AF_PROGRAM_BUDGET"); programAssert(ownerResources.liabilityTokens + ownerResources.finalReservedTokens + reservationTokens - (finalAssessment ? Math.min(reservationTokens, ownerResources.runs.find(run => run.runId === runId)?.finalReservedTokens ?? 0) : 0) <= this.runtime.ownerMaxTokens, "Owner admission token budget exhausted", "AF_PROGRAM_BUDGET"); }
468
549
  await this.store.transact(runId, "attempt-dispatch-intent", current => {
469
550
  programAssert(current?.status === "executing" && current.totalAttempts < policy.maxAttempts, "Attempt budget exhausted or run stopped");
470
- current.totalAttempts++; current.attempts[attemptId] = { attemptId, operationId: id, generation: current.operations[id].generation, inputDigest, executorDigest: programDigest(executor), outcome: "running", startedAt: new Date().toISOString(), reservation: { scopes, status: "held", ownerEpoch: this.owner.epoch } };
551
+ current.totalAttempts++; current.attempts[attemptId] = { attemptId, operationId: id, generation: current.operations[id].generation, inputDigest, executorDigest: programDigest(executor), outcome: "running", startedAt: new Date().toISOString(), ...(recovery?.threadId ? { threadId: recovery.threadId } : {}), usageApplicability: executor.tokenAccounting === "none" ? "not-applicable" : "provider", reservation: { scopes, status: "held", ownerEpoch: this.owner.epoch }, ...(policy.resources ? { resourceReservation: { tokens: reservationTokens, status: "held" as const, phase: finalAssessment ? "final" as const : "work" as const } } : {}), ...(preparation.recovery ? { recovery: preparation.recovery } : {}) };
471
552
  current.operations[id].attempts.push(attemptId); current.queue = current.queue?.filter(entry => entry.id !== id); return current;
472
553
  });
554
+ });
555
+ dispatched = true;
556
+ for (const observation of pendingObservations) await onObservation(observation);
473
557
  const result = await this.cancellable(preparation.execute(), controller.signal, () => ({ outcome: "uncertain" as const, reason: "Worker did not confirm cancellation; reservation retained" }), 1000); await this.owner.assert(); completionObserved = result.outcome === "completed"; clearTimeout(timeout); parentSignal.removeEventListener("abort", abort);
558
+ if (result.usage !== undefined) await this.store.transact(runId, "worker-final-usage", current => { const attempt = current!.attempts[attemptId]; programAssert(attempt?.outcome === "running" && attempt.generation === current!.operations[id].generation, "Fenced usage result"); attempt.usage = result.usage; const usage = result.usage as { input_tokens?: number; cached_input_tokens?: number; output_tokens?: number }; if (summarizeProgramUsage(attempt).certainty === "unknown" && Number.isSafeInteger(usage.input_tokens) && Number.isSafeInteger(usage.output_tokens)) recordProgramObservation(attempt, { id: `${attemptId}:final-usage`, type: "usage", source: "adapter-result", at: new Date().toISOString(), usage: usage as NonNullable<ProgramWorkerObservation["usage"]> }); attempt.usageSummary = summarizeProgramUsage(attempt); return current!; });
474
559
  if (result.outcome !== "completed") {
475
- await this.store.transact(runId, "attempt-failed", current => { const attempt = current!.attempts[attemptId]; if (attempt.outcome === "running" && attempt.generation === current!.operations[id].generation) { attempt.outcome = result.outcome; attempt.completedAt = new Date().toISOString(); attempt.reason = result.reason ?? result.outcome; if (attempt.reservation && result.outcome !== "uncertain") attempt.reservation.status = "released"; } return current!; });
560
+ await this.store.transact(runId, "attempt-failed", current => { const attempt = current!.attempts[attemptId]; if (attempt.outcome === "running" && attempt.generation === current!.operations[id].generation) { attempt.outcome = result.outcome; attempt.completedAt = new Date().toISOString(); attempt.reason = result.reason ?? result.outcome; if (result.outcome !== "uncertain") { if (attempt.reservation) attempt.reservation.status = "released"; if (attempt.resourceReservation) attempt.resourceReservation.status = "released"; } } return current!; });
476
561
  throw new ProgramError("AF_PROGRAM_ACTIVITY", result.reason ?? result.outcome);
477
562
  }
478
563
  validateProgramData(result.data, registryEntry(run.registry.schemas, executor.schema));
@@ -502,7 +587,7 @@ export class ProgramRunService {
502
587
  }
503
588
  const output: ActivityOutput = { data: result.data, candidate: next, attemptId, ...(evidenceRefs.length ? { evidenceRefs } : {}) }, outputRef = await this.store.put(output, policy.maxOutputBytes);
504
589
  await this.store.transact(runId, "attempt-result", current => {
505
- const attempt = current!.attempts[attemptId]; programAssert(attempt.outcome === "running" && attempt.generation === current!.operations[id].generation, "Fenced attempt result"); attempt.outcome = "completed"; attempt.completedAt = new Date().toISOString(); attempt.outputRef = outputRef; if (attempt.reservation) attempt.reservation.status = "released";
590
+ const attempt = current!.attempts[attemptId]; programAssert(attempt.outcome === "running" && attempt.generation === current!.operations[id].generation, "Fenced attempt result"); attempt.outcome = "completed"; attempt.completedAt = new Date().toISOString(); attempt.outputRef = outputRef; if (attempt.reservation) attempt.reservation.status = "released"; if (attempt.resourceReservation) attempt.resourceReservation.status = "released";
506
591
  Object.assign(current!.artifactRecords ??= {}, captureRecords);
507
592
  if (deltaId && deltaRecord) current!.deltas[deltaId] = deltaRecord;
508
593
  if (result.usage !== undefined) attempt.usage = result.usage;
@@ -510,7 +595,7 @@ export class ProgramRunService {
510
595
  const operation = current!.operations[id]; operation.status = "completed"; operation.outputRef = outputRef; return current!;
511
596
  }); return output;
512
597
  } catch (error) {
513
- await this.store.transact(runId, "dispatch-uncertainty", current => { const attempt = current!.attempts[attemptId]; if (attempt?.outcome === "running") { attempt.outcome = completionObserved ? "invalid_output" : "uncertain"; attempt.reason = (error as Error).message.slice(0, 1000); current!.operations[id].status = completionObserved ? "needs-attention" : "uncertain"; if (completionObserved && attempt.reservation) attempt.reservation.status = "released"; } return current!; }); throw error;
598
+ await this.store.transact(runId, "dispatch-uncertainty", current => { const attempt = current!.attempts[attemptId]; if (attempt?.outcome === "running") { attempt.outcome = completionObserved ? "invalid_output" : "uncertain"; attempt.reason = (error as Error).message.slice(0, 1000); current!.operations[id].status = completionObserved ? "needs-attention" : "uncertain"; if (completionObserved) { if (attempt.reservation) attempt.reservation.status = "released"; if (attempt.resourceReservation) attempt.resourceReservation.status = "released"; } } return current!; }); throw error;
514
599
  } finally { if (timeout) clearTimeout(timeout); removeAbort?.(); this.attemptControllers.delete(attemptId); const observed = (await this.state(runId)).attempts[attemptId];
515
600
  if (!observed || observed.outcome !== "uncertain") { release(); this.reservations.delete(attemptId); } }
516
601
  }
@@ -591,12 +676,48 @@ export class ProgramRunService {
591
676
  programAssert(candidate.baselineDigest === run.baselineDigest && run.candidates[candidate.candidateId]?.digest === candidate.digest, "Candidate ancestry/baseline mismatch");
592
677
  for (const ref of candidate.deltas) if (!seen.has(ref)) { seen.add(ref); refs.push(ref); }
593
678
  }
594
- const artifacts = await Promise.all(refs.map(ref => { programAssert(run.deltas[ref], "Composition delta missing"); return this.store.get<ManagedArtifact>(run.deltas[ref].artifactRef); })), writers = new Map<string, string>();
679
+ const artifacts = await Promise.all(refs.map(ref => { programAssert(run.deltas[ref], "Composition delta missing"); return this.store.get<ManagedArtifact>(run.deltas[ref].artifactRef); })), writers = new Map<string, string>(), conflicts = new Set<string>();
680
+ interface WriterNode { records: { path: string; prior: string }[]; children: Map<string, WriterNode> }
681
+ const root: WriterNode = { records: [], children: new Map() };
595
682
  for (let index = 0; index < artifacts.length; index++) for (const file of artifacts[index].files) {
596
- const prior = writers.get(file.path);
597
- if (prior) programAssert(candidates.some(candidate => candidate.deltas.includes(prior) && candidate.deltas.includes(refs[index]) && candidate.deltas.indexOf(prior) < candidate.deltas.indexOf(refs[index])), `Independent writers conflict at ${file.path}`);
683
+ const compare = ({ path, prior }: { path: string; prior: string }) => {
684
+ if (prior === refs[index] || candidates.some(candidate => candidate.deltas.includes(prior) && candidate.deltas.includes(refs[index]) && candidate.deltas.indexOf(prior) < candidate.deltas.indexOf(refs[index]))) return;
685
+ conflicts.add(path); conflicts.add(file.path);
686
+ };
687
+ let node = root;
688
+ for (const segment of file.path.toLowerCase().split("/")) {
689
+ for (const record of node.records) compare(record);
690
+ if (!node.children.has(segment)) node.children.set(segment, { records: [], children: new Map() });
691
+ node = node.children.get(segment)!;
692
+ }
693
+ const descendants = [node];
694
+ while (descendants.length) { const next = descendants.pop()!; for (const record of next.records) compare(record); descendants.push(...next.children.values()); }
695
+ node.records.push({ path: file.path, prior: refs[index] });
598
696
  writers.set(file.path, refs[index]);
599
697
  }
698
+ if (conflicts.size) {
699
+ const acceptance = registryEntry(run.registry.acceptance, run.program.acceptance);
700
+ programAssert(input.resolver, `Independent writers conflict at ${[...conflicts].join(", ")}`);
701
+ const resolver = registryEntry(run.registry.executors, input.resolver as ProgramRef);
702
+ programAssert(resolver.effect === "isolated-write" && resolver.role === "implementer", "Resolver must produce an isolated candidate");
703
+ const scope = (input.resolverWriteScope ?? [...writers.keys()]) as string[];
704
+ programAssert([...writers.keys()].every(path => programWithin(path, scope)), "Resolution scope must cover all proposed files");
705
+ programValueBytes({ artifacts, candidates }, policy.maxOutputBytes, policy.maxItems);
706
+ const baseline = await this.initialCandidate(runId);
707
+ const resolution = await this.activity(runId, `${id}/resolution`, input.resolver as ProgramRef, { input: input.resolverInput ?? {}, conflicts: [...conflicts], candidates, artifacts, instruction: "Resolve all proposed changes against the baseline; this output requires fresh final assessment." }, baseline, scope, context);
708
+ programAssert(resolution.candidate.digest !== baseline.digest || acceptance.allowNoWork, "Conflict resolution produced no changes outside allowNoWork contract");
709
+ if (resolution.candidate.digest === baseline.digest) {
710
+ resolution.candidate = { ...baseline, candidateId: `candidate-resolution-${resolution.attemptId}`, parents: [baseline.candidateId], producerAttempts: [resolution.attemptId] };
711
+ await this.store.transact(runId, "composition-no-work-candidate", current => { current!.candidates[resolution.candidate.candidateId] = resolution.candidate; return current!; });
712
+ }
713
+ await this.store.transact(runId, "composition-resolution", current => {
714
+ const operation = current!.operations[id], receiptId = `resolution-${resolution.attemptId}`;
715
+ programAssert(operation && current!.attempts[resolution.attemptId]?.outcome === "completed", "Unobserved conflict resolution");
716
+ (current!.compositionResolutions ??= {})[receiptId] = { receiptId, candidateId: resolution.candidate.candidateId, candidateDigest: resolution.candidate.digest, originalCandidateIds: candidates.map(candidate => candidate.candidateId), attemptId: resolution.attemptId, operationId: id, generation: operation.generation, semanticDigest: operation.semanticDigest, inputDigest: operation.inputDigest, populationDigest: run.program.population ? programDigest(registryEntry(run.registry.populations, run.program.population)) : programDigest(null), acceptanceDigest: programDigest(acceptance) };
717
+ return current!;
718
+ });
719
+ return { candidate: resolution.candidate, resolution: { attemptId: resolution.attemptId, conflictingPaths: [...conflicts], requiresFinalAssessment: true } };
720
+ }
600
721
  const base = run.baseRef ? await this.store.get<ManagedBase>(run.baseRef) : undefined, digest = base ? (await previewManagedArtifacts(base, artifacts)).digest : programDigest({ candidates: candidates.map(candidate => candidate.digest), refs });
601
722
  const parents = candidates.map(candidate => candidate.candidateId), producerAttempts = [...new Set(candidates.flatMap(candidate => candidate.producerAttempts))];
602
723
  const candidate: ProgramCandidate = { candidateId: `candidate-${programDigest({ digest, id, parents, refs, producerAttempts }).slice(7, 39)}`, digest, baselineDigest: run.baselineDigest, parents, deltas: refs, producerAttempts };
@@ -660,28 +781,32 @@ export class ProgramRunService {
660
781
  const environmentRef = programDigest({ baseline: run.baselineDigest, inputRef: run.inputRef, policy: run.program.policy });
661
782
  const assess = async (): Promise<ProgramAssessment> => {
662
783
  const candidate = checkpoint.candidate;
663
- const assessmentContext: ProgramAssessmentContext = { runId, invocationId: id, assessmentId: `${id}/assessment-${checkpoint.assessments}`, phase, candidateRef: candidate, obligationIds, ...(itemKey ? { itemKey } : {}), environmentRef, evidenceRefs: [], input: input.input ?? null, ...(population?.visualCases ? { visualCases: population.visualCases.filter(entry => expectedMembers.includes(canonicalItemKey(entry.itemKey)) && (phase === "final" || canonicalItemKey(entry.itemKey) === itemKey)) } : {}) };
784
+ const candidateResolutions = phase === "final" ? this.compositionResolutions(await this.state(runId), candidate) : [];
785
+ const resolutionObligations = candidateResolutions.flatMap(receipt => expectedMembers.map(key => `${receipt.receiptId}/${programSegment(key)}`));
786
+ const assessedObligations = [...new Set([...obligationIds, ...resolutionObligations])];
787
+ const assessmentContext: ProgramAssessmentContext = { runId, invocationId: id, assessmentId: `${id}/assessment-${checkpoint.assessments}`, phase, candidateRef: candidate, obligationIds: assessedObligations, ...(itemKey ? { itemKey } : {}), environmentRef, evidenceRefs: [], input: input.input ?? null, ...(population?.visualCases ? { visualCases: population.visualCases.filter(entry => expectedMembers.includes(canonicalItemKey(entry.itemKey)) && (phase === "final" || canonicalItemKey(entry.itemKey) === itemKey)) } : {}) };
664
788
  const evidenceAttempts: string[] = [];
665
789
  for (const [index, executor] of ((input.evidence ?? []) as ProgramRef[]).entries()) {
666
- const evidence = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/evidence-${index}`, executor, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest }, candidate, [], context);
790
+ const evidence = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/evidence-${index}`, executor, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest }, candidate, [], context, phase === "final");
667
791
  assessmentContext.evidenceRefs.push(...(evidence.evidenceRefs ?? [])); evidenceAttempts.push(evidence.attemptId);
668
792
  }
669
793
  const observed = await this.state(runId);
670
794
  const requiredCaptureKeys = captureKeys(acceptance, phase, expectedMembers, itemKey);
671
795
  for (const key of requiredCaptureKeys) programAssert(assessmentContext.evidenceRefs.some(ref => { const capture = observed.artifactRecords?.[ref]; return capture?.itemKey === key && capture.candidateDigest === candidate.digest && capture.environmentRef === environmentRef && observed.attempts[capture.producerAttemptId]?.outcome === "completed"; }), `Missing current capture for ${key}`);
672
- const review = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/review`, input.review as ProgramRef, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest, feedback: checkpoint.feedback }, candidate, [], context), report = asRecord(review.data);
796
+ const review = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/review`, input.review as ProgramRef, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest, feedback: checkpoint.feedback }, candidate, [], context, phase === "final"), report = asRecord(review.data);
673
797
  programAssert(["approved", "changes_requested", "inconclusive"].includes(String(report.verdict)) && Array.isArray(report.findings), "Review verdict/findings required");
674
798
  const outcomes: { id: string; passed: boolean }[] = [], attemptIds = [...evidenceAttempts, review.attemptId];
675
- for (const check of checks) {
799
+ const assessmentChecks = [...new Set([...checks, ...(candidateResolutions.length ? acceptance.requiredChecksByScope?.item ?? acceptance.requiredChecks : [])])];
800
+ for (const check of assessmentChecks) {
676
801
  programAssert(check.includes("@"), "Check must be versioned executor reference"); const split = check.lastIndexOf("@"), reference = { id: check.slice(0, split), version: check.slice(split + 1) }, checker = registryEntry(run.registry.executors, reference);
677
802
  programAssert(checker.kind === "command" && checker.effect === "read" && programDigest(checker) !== programDigest(reviewer), "Checks require distinct observed command executors");
678
- const output = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/check-${encodeURIComponent(check)}`, reference, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest }, candidate, [], context);
803
+ const output = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/check-${encodeURIComponent(check)}`, reference, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest }, candidate, [], context, phase === "final");
679
804
  outcomes.push({ id: check, passed: asRecord(output.data).passed === true }); attemptIds.push(output.attemptId);
680
805
  }
681
806
  const covered = Array.isArray(report.coveredObligationIds) ? report.coveredObligationIds as string[] : [];
682
807
  if (obligationIds.some(obligation => !covered.includes(obligation))) { report.verdict = "inconclusive"; report.findings = []; }
683
- const satisfied = report.verdict === "approved" && !(report.findings as unknown[]).length && outcomes.every(outcome => outcome.passed) ? obligationIds.filter(obligation => covered.includes(obligation)) : [];
684
- const assessment: ProgramAssessment = { phase, obligationIds, satisfiedObligationIds: satisfied, unsatisfiedObligationIds: obligationIds.filter(obligation => !satisfied.includes(obligation)), environmentRef, evidenceRefs: assessmentContext.evidenceRefs, contractDigest: programDigest(acceptance), receiptId: `assessment-${randomUUID()}`, operationId: id, generation: origin.generation, semanticDigest: origin.semanticDigest, inputDigest: origin.inputDigest, candidateDigest: candidate.digest, verdict: report.verdict as ProgramAssessment["verdict"], findings: report.findings as unknown[], checks: outcomes, attemptIds };
808
+ const satisfied = report.verdict === "approved" && !(report.findings as unknown[]).length && outcomes.every(outcome => outcome.passed) ? assessedObligations.filter(obligation => covered.includes(obligation)) : [];
809
+ const assessment: ProgramAssessment = { phase, resolutionReceiptIds: candidateResolutions.map(receipt => receipt.receiptId), obligationIds: assessedObligations, satisfiedObligationIds: satisfied, unsatisfiedObligationIds: assessedObligations.filter(obligation => !satisfied.includes(obligation)), environmentRef, evidenceRefs: assessmentContext.evidenceRefs, contractDigest: programDigest(acceptance), receiptId: `assessment-${randomUUID()}`, operationId: id, generation: origin.generation, semanticDigest: origin.semanticDigest, inputDigest: origin.inputDigest, candidateDigest: candidate.digest, verdict: report.verdict as ProgramAssessment["verdict"], findings: report.findings as unknown[], checks: outcomes, attemptIds };
685
810
  await this.store.put(assessment);
686
811
  checkpoint.assessment = assessment; checkpoint.phase = "decide";
687
812
  await this.store.transact(runId, "assessment-receipt-and-recipe", current => { programAssert(current!.operations[id].generation === origin.generation && current!.operations[id].semanticDigest === origin.semanticDigest && attemptIds.every(attemptId => current!.attempts[attemptId]?.outcome === "completed") && !candidate.producerAttempts.includes(review.attemptId), "Assessment lacks current distinct observed attempts"); current!.assessments[assessment.receiptId!] = assessment; current!.repairs[id] = structuredClone(checkpoint); return current!; });
@@ -721,25 +846,37 @@ export class ProgramRunService {
721
846
  }
722
847
  }
723
848
  }
849
+ private compositionResolutions(run: ProgramRunV2, candidate: ProgramCandidate) {
850
+ return Object.values(run.compositionResolutions ?? {}).filter(receipt => {
851
+ const resolved = run.candidates[receipt.candidateId];
852
+ return resolved && resolved.deltas.every(delta => candidate.deltas.includes(delta)) && resolved.producerAttempts.every(attempt => candidate.producerAttempts.includes(attempt));
853
+ });
854
+ }
724
855
  private async gate(runId: string, id: string, input: Record<string, unknown>): Promise<unknown> {
725
856
  const run = await this.state(runId), candidate = this.resolveCandidate(run, input.candidate), acceptance = registryEntry(run.registry.acceptance, run.program.acceptance), policy = registryEntry(run.registry.policies, run.program.policy);
726
857
  programAssert(run.candidates[candidate.candidateId]?.digest === candidate.digest, "Unregistered candidate");
727
858
  programAssert(input.obligations === undefined || programDigest(input.obligations) === programDigest(acceptance), "Gate cannot replace owner acceptance obligations");
728
859
  programAssert(!Object.values(run.attempts).some(attempt => attempt.outcome === "uncertain"), "Uncertain effects block gate");
729
860
  programAssert(run.program.mode !== "data", "Data profile cannot grant candidate publication");
730
- const requiredChecks = acceptance.requiredChecksByScope?.final ?? acceptance.requiredChecks;
861
+ const resolutions = this.compositionResolutions(run, candidate);
862
+ for (const receipt of resolutions) {
863
+ const origin = run.operations[receipt.operationId], attempt = run.attempts[receipt.attemptId];
864
+ programAssert(origin?.status === "completed" && !origin.retired && origin.generation === receipt.generation && origin.semanticDigest === receipt.semanticDigest && origin.inputDigest === receipt.inputDigest && attempt?.outcome === "completed" && run.candidates[receipt.candidateId]?.digest === receipt.candidateDigest && receipt.acceptanceDigest === programDigest(acceptance) && receipt.populationDigest === (run.program.population ? programDigest(registryEntry(run.registry.populations, run.program.population)) : programDigest(null)), "Composition resolution receipt is stale");
865
+ }
866
+ const requiredChecks = [...new Set([...(acceptance.requiredChecksByScope?.final ?? acceptance.requiredChecks), ...(resolutions.length ? acceptance.requiredChecksByScope?.item ?? acceptance.requiredChecks : [])])];
731
867
  const expectedMembers = run.program.population ? registryEntry(run.registry.populations, run.program.population).members.filter(key => !registryEntry(run.registry.populations, run.program.population!).exclusions.some(exclusion => exclusion.id === key)).map(canonicalItemKey) : [];
732
868
  const requiredObligations = (acceptance.obligations ?? []).flatMap(obligation => obligation.scope === "final" ? [obligation.id] : expectedMembers.map(key => `${obligation.id}/${programSegment(key)}`));
869
+ requiredObligations.push(...resolutions.flatMap(receipt => expectedMembers.map(key => `${receipt.receiptId}/${programSegment(key)}`)));
733
870
  const assessment = Object.values(run.assessments).find(assessment => {
734
871
  const origin = assessment.operationId ? run.operations[assessment.operationId] : undefined;
735
- return origin && origin.status === "completed" && origin.generation === assessment.generation && origin.semanticDigest === assessment.semanticDigest && origin.inputDigest === assessment.inputDigest && assessment.candidateDigest === candidate.digest && assessment.verdict === "approved" && !assessment.findings.length && assessment.attemptIds.every(attemptId => run.attempts[attemptId]?.outcome === "completed") && (!acceptance.requiredChecksByScope || assessment.phase === "final") && requiredObligations.every(obligation => assessment.satisfiedObligationIds?.includes(obligation)) && requiredChecks.every(check => assessment.checks.some(outcome => outcome.id === check && outcome.passed));
872
+ return origin && origin.status === "completed" && origin.generation === assessment.generation && origin.semanticDigest === assessment.semanticDigest && origin.inputDigest === assessment.inputDigest && assessment.candidateDigest === candidate.digest && resolutions.every(receipt => assessment.resolutionReceiptIds?.includes(receipt.receiptId)) && assessment.verdict === "approved" && !assessment.findings.length && assessment.attemptIds.every(attemptId => run.attempts[attemptId]?.outcome === "completed") && (!acceptance.requiredChecksByScope && !resolutions.length || assessment.phase === "final") && requiredObligations.every(obligation => assessment.satisfiedObligationIds?.includes(obligation)) && requiredChecks.every(check => assessment.checks.some(outcome => outcome.id === check && outcome.passed));
736
873
  });
737
- programAssert(!acceptance.requireReview || assessment, "Applicable distinct review/checks missing");
874
+ programAssert(!acceptance.requireReview && !resolutions.length || assessment, "Applicable distinct review/checks missing");
738
875
  programAssert(assessment || requiredChecks.length === 0, "Required checks missing");
739
876
  if (run.program.population) {
740
877
  const coverage = asRecord(input.coverage), registered = run.coverageReceipts[String(coverage.receiptId)];
741
878
  programAssert(registered && programDigest(Object.fromEntries(Object.entries(coverage).filter(([key]) => key !== "receiptId"))) === programDigest(registered) && registered.populationDigest === programDigest(registryEntry(run.registry.populations, run.program.population)), "Owner coverage receipt missing/mismatched");
742
- programAssert(Object.entries(run.collections).some(([operationId, collection]) => collection.coverageReceiptId === coverage.receiptId && collection.obligationsSatisfied && collection.seal === run.seals[operationId] && run.operations[operationId]?.status === "completed" && run.operations[operationId]?.generation === collection.generation && programDigest(collection.ids) === programDigest(registered.ids) && collection.acceptedCandidateIds.every(candidateId => { const accepted = run.candidates[candidateId]; return accepted && accepted.deltas.every(delta => candidate.deltas.includes(delta)) && accepted.producerAttempts.every(attempt => candidate.producerAttempts.includes(attempt)); }) && collection.acceptedAssessmentIds.every(receiptId => {
879
+ programAssert(Object.entries(run.collections).some(([operationId, collection]) => collection.coverageReceiptId === coverage.receiptId && collection.obligationsSatisfied && collection.seal === run.seals[operationId] && run.operations[operationId]?.status === "completed" && run.operations[operationId]?.generation === collection.generation && programDigest(collection.ids) === programDigest(registered.ids) && collection.acceptedCandidateIds.every(candidateId => { const accepted = run.candidates[candidateId]; return accepted && (accepted.deltas.every(delta => candidate.deltas.includes(delta)) && accepted.producerAttempts.every(attempt => candidate.producerAttempts.includes(attempt)) || !!assessment && resolutions.some(receipt => receipt.originalCandidateIds.some(originalId => { const original = run.candidates[originalId]; return original && accepted.deltas.every(delta => original.deltas.includes(delta)) && accepted.producerAttempts.every(attempt => original.producerAttempts.includes(attempt)); }))); }) && collection.acceptedAssessmentIds.every(receiptId => {
743
880
  const receipt = run.assessments[receiptId], origin = receipt?.operationId ? run.operations[receipt.operationId] : undefined;
744
881
  return origin?.status === "completed" && origin.generation === receipt.generation && receipt.verdict === "approved" && !receipt.findings.length && receipt.attemptIds.every(attemptId => run.attempts[attemptId]?.outcome === "completed") && (acceptance.requiredChecksByScope?.item ?? acceptance.requiredChecks).every(check => receipt.checks.some(outcome => outcome.id === check && outcome.passed));
745
882
  })), "Population gate requires closed item acceptance, not discovery coverage alone");
@@ -17,7 +17,7 @@ async function retrySharing<T>(action: () => Promise<T>): Promise<T> {
17
17
  }
18
18
  /** One authoritative atomic record per run; artifacts commit before referring transitions. */
19
19
  export class ProgramRunStore {
20
- readonly metrics = { transactions: 0, transactionMs: 0, bytesWritten: 0, snapshotBytes: 0, journalBytes: 0, envelopeBytes: 0, artifactBytes: 0, artifactReads: 0 };
20
+ readonly metrics = { transactions: 0, transactionMs: 0, bytesWritten: 0, snapshotBytes: 0, journalBytes: 0, envelopeBytes: 0, artifactBytes: 0, artifactReads: 0, deduplicatedWrites: 0, deduplicatedBytes: 0 };
21
21
  private tails = new Map<string, Promise<unknown>>();
22
22
  private definitions = new Set<string>();
23
23
  private validate(state: ProgramRunV2): void {
@@ -34,6 +34,10 @@ export class ProgramRunStore {
34
34
  async put(value: unknown, maxBytes = 4 * 1024 * 1024, category: "snapshotBytes" | "journalBytes" | "artifactBytes" = "artifactBytes"): Promise<string> {
35
35
  const bytes = stableStringify(value), digest = programDigest(value); programAssert(Buffer.byteLength(bytes) <= maxBytes, "Artifact too large");
36
36
  const path = join(this.directory, "artifacts", `${digest.slice(7)}.json`); await assertAttachedSafePath(this.root, path);
37
+ try {
38
+ programAssert(await readFile(path, "utf8") === bytes, "Immutable artifact corrupted");
39
+ this.metrics.deduplicatedWrites++; this.metrics.deduplicatedBytes += Buffer.byteLength(bytes); return digest;
40
+ } catch (error) { if (!absent(error)) throw error; }
37
41
  const temp = `${path}.${randomUUID()}.tmp`, handle = await open(temp, "wx", 0o600);
38
42
  try { await handle.writeFile(bytes); await handle.sync(); this.metrics.bytesWritten += Buffer.byteLength(bytes); this.metrics[category] += Buffer.byteLength(bytes); } finally { await handle.close(); }
39
43
  try {
@@ -53,6 +57,10 @@ export class ProgramRunStore {
53
57
  programAssert(bytes.byteLength <= maxBytes, "Binary artifact exceeds byte budget");
54
58
  const digest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`, path = join(this.directory, "artifacts", `${digest.slice(7)}.blob`);
55
59
  await assertAttachedSafePath(this.root, path);
60
+ try {
61
+ programAssert((await readFile(path)).equals(Buffer.from(bytes)), "Immutable binary corrupted");
62
+ this.metrics.deduplicatedWrites++; this.metrics.deduplicatedBytes += bytes.byteLength; return digest;
63
+ } catch (error) { if (!absent(error)) throw error; }
56
64
  const temp = `${path}.${randomUUID()}.tmp`, handle = await open(temp, "wx", 0o600);
57
65
  try { await handle.writeFile(bytes); await handle.sync(); this.metrics.bytesWritten += bytes.byteLength; this.metrics.artifactBytes += bytes.byteLength; } finally { await handle.close(); }
58
66
  try { try { programAssert((await readFile(path)).equals(Buffer.from(bytes)), "Immutable binary corrupted"); } catch (error) { if (!absent(error)) throw error; await retrySharing(() => rename(temp, path)); } }
@@ -0,0 +1,96 @@
1
+ import { programAssert, programDigest, programWithin, refKey, registryEntry, validateProgramData, type ProgramRef, type ProgramRegistry, type WorkflowProgramV2 } from "./program-contract.ts";
2
+ import { validateProgramCapabilities } from "./program-worker.ts";
3
+
4
+ export type ProgramTemplateKind = "review" | "bugfix" | "migration";
5
+ export interface ProgramTemplateOptions {
6
+ id?: string;
7
+ inputSchema: ProgramRef; outputSchema: ProgramRef; policy: ProgramRef; acceptance: ProgramRef;
8
+ implement: ProgramRef; review: ProgramRef; population?: ProgramRef;
9
+ resolver?: ProgramRef; resolverWriteScope?: string[];
10
+ evidence?: ProgramRef[];
11
+ approvalSchema?: ProgramRef;
12
+ }
13
+ const expr = ($expr: string, ...args: unknown[]) => ({ $expr, args });
14
+
15
+ /** Owner references only. This constructs finite data; it never dispatches workers. */
16
+ export function buildWorkflowTemplate(kind: ProgramTemplateKind, options: ProgramTemplateOptions, registry: ProgramRegistry): WorkflowProgramV2 {
17
+ programAssert(["review", "bugfix", "migration"].includes(kind), "Unknown workflow template");
18
+ programAssert(options.id === undefined || typeof options.id === "string" && options.id.length > 0 && options.id.length <= 200, "Template identity exceeds supported event correlation bounds");
19
+ const acceptance = registryEntry(registry.acceptance, options.acceptance);
20
+ const policy = registryEntry(registry.policies, options.policy);
21
+ const reviewer = registryEntry(registry.executors, options.review);
22
+ const implementer = registryEntry(registry.executors, options.implement);
23
+ const writeScope = [...new Set([...acceptance.writeScope, ...policy.writeScope, ...implementer.writeScope])].filter(path => [acceptance.writeScope, policy.writeScope, implementer.writeScope].every(scope => programWithin(path, scope)));
24
+ programAssert(kind === "review" || implementer.effect === "isolated-write" && implementer.role === "implementer" && writeScope.length > 0, "Writing templates require an authorized implementer scope");
25
+ validateProgramCapabilities(reviewer, policy, acceptance, []);
26
+ if (kind !== "review") validateProgramCapabilities(implementer, policy, acceptance, writeScope);
27
+ const captureRequired = (acceptance.obligations ?? []).some(obligation => obligation.requiredEvidence?.includes("capture"));
28
+ programAssert((acceptance.obligations ?? []).every(obligation => (obligation.requiredEvidence ?? []).every(evidence => evidence === "capture")), "Template cannot attest unsupported evidence obligations");
29
+ programAssert(!captureRequired || kind === "migration" && options.population && options.evidence?.length, "Capture obligations require a population migration and evidence executors");
30
+ for (const ref of options.evidence ?? []) {
31
+ const executor = registryEntry(registry.executors, ref);
32
+ programAssert(executor.effect === "read", "Template evidence must be readonly");
33
+ validateProgramCapabilities(executor, policy, acceptance, []);
34
+ }
35
+ if (acceptance.requireHumanApproval) {
36
+ programAssert(options.approvalSchema, "Human approval requires an owner-registered approval schema");
37
+ const schema = registryEntry(registry.schemas, options.approvalSchema);
38
+ programAssert(!schema.type || (Array.isArray(schema.type) ? schema.type : [schema.type]).includes("object"), "Approval schema must allow object payloads");
39
+ if (schema.properties?.approved) validateProgramData(true, schema.properties.approved);
40
+ if (schema.const) programAssert(typeof schema.const === "object" && !Array.isArray(schema.const) && schema.const.approved === true, "Approval constant must authorize approval");
41
+ }
42
+ let resolverWriteScope: string[] | undefined;
43
+ if (options.resolver) {
44
+ const resolver = registryEntry(registry.executors, options.resolver);
45
+ programAssert(kind === "migration" && resolver.role === "implementer" && resolver.effect === "isolated-write" && policy.executors.includes(refKey(options.resolver)), "Migration resolver must be an owner-authorized implementer");
46
+ const intersection = [...new Set([...acceptance.writeScope, ...policy.writeScope, ...resolver.writeScope])].filter(path => [acceptance.writeScope, policy.writeScope, resolver.writeScope].every(scope => programWithin(path, scope)));
47
+ resolverWriteScope = validateProgramCapabilities(resolver, policy, acceptance, options.resolverWriteScope ?? intersection);
48
+ }
49
+ programAssert(!options.resolverWriteScope || options.resolver, "Resolver scope requires a resolver");
50
+ programAssert(reviewer.role === "reviewer" && reviewer.effect === "read", "Template requires a readonly reviewer");
51
+ programAssert(programDigest(reviewer) !== programDigest(implementer), "Template requires a distinct reviewer");
52
+ for (const ref of [options.implement, options.review]) programAssert(policy.executors.includes(refKey(ref)), "Template executor not authorized by owner policy");
53
+ for (const key of new Set([...acceptance.requiredChecks, ...(acceptance.requiredChecksByScope?.item ?? []), ...(acceptance.requiredChecksByScope?.final ?? [])])) {
54
+ const checker = registry.executors[key];
55
+ programAssert(checker?.kind === "command" && checker.effect === "read" && policy.executors.includes(key), `Required check not an authorized readonly command: ${key}`);
56
+ programAssert(programDigest(checker) !== programDigest(reviewer), "Required check must be distinct from reviewer");
57
+ validateProgramCapabilities(checker, policy, acceptance, []);
58
+ }
59
+ const scoped = acceptance.requiredChecksByScope;
60
+ const repair = (id: string, initialCandidate: unknown, phase: "item" | "final", entryMode: "assess-first" | "implement-first", scope: unknown) => ({
61
+ kind: "repair", id, options: {
62
+ recipe: { id: "repair", version: "v2" }, implement: options.implement, review: options.review,
63
+ checks: scoped?.[phase] ?? acceptance.requiredChecks,
64
+ ...(options.evidence?.length ? { evidence: options.evidence } : {}),
65
+ ...(scoped ? { assessmentScope: phase } : {}), entryMode, initialCandidate, writeScope: scope,
66
+ maxRepairRounds: kind === "review" ? 1 : 3, maxAssessmentAttempts: 6, maxInfrastructureAttempts: 2,
67
+ progressPolicy: { unchangedCandidateRounds: 1, repeatedFindingsRounds: 2 },
68
+ },
69
+ });
70
+ const program: WorkflowProgramV2 = {
71
+ schemaVersion: 2, operatorVersion: 2, mode: "candidate", id: options.id ?? `${kind}-workflow`, version: 1,
72
+ inputSchema: options.inputSchema, outputSchema: options.outputSchema, policy: options.policy, acceptance: options.acceptance,
73
+ steps: [], result: expr("output", "final"),
74
+ };
75
+ if (kind === "migration") {
76
+ programAssert(options.population, "Migration requires an owner population");
77
+ registryEntry(registry.populations, options.population);
78
+ program.population = options.population;
79
+ const items = expr("field", expr("workflowInput"), "items");
80
+ program.steps.push({ kind: "map", id: "migrate", options: {
81
+ items, key: expr("field", expr("item"), "id"),
82
+ coverage: expr("coverageFor", expr("population"), items), completion: "all-required", concurrency: policy.concurrency,
83
+ body: repair("component", expr("candidateFromBaseline", expr("item")), "item", "assess-first", expr("field", expr("item"), "allowedPaths")),
84
+ } }, { kind: "compose", id: "integrate", options: { candidates: expr("acceptedCandidates", "migrate"), onConflict: "needs-resolution", ...(options.resolver ? { resolver: options.resolver, resolverWriteScope } : {}) } });
85
+ }
86
+ program.steps.push(repair("assessment", kind === "migration" ? expr("outputCandidate", "integrate") : expr("candidateFromBaseline"), "final", "assess-first", kind === "review" ? [] : writeScope));
87
+ if (acceptance.requireHumanApproval) program.steps.push({ kind: "waitEvent", id: "human-approval", options: {
88
+ schema: options.approvalSchema!, type: "human-approval", correlation: `${program.id}:approval`, timeoutMs: policy.deadlineMs,
89
+ subject: expr("object", { candidate: expr("acceptedCandidate", "assessment"), contract: expr("acceptance") }),
90
+ } });
91
+ program.steps.push({ kind: "gate", id: "final", options: {
92
+ candidate: expr("acceptedCandidate", "assessment"), ...(kind === "migration" ? { coverage: expr("coverageReceipt", "migrate") } : {}),
93
+ ...(acceptance.requireHumanApproval ? { authorization: expr("output", "human-approval") } : {}),
94
+ } });
95
+ return program;
96
+ }