forgeos 0.1.0-alpha.64 → 0.1.0-alpha.65
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/AGENTS.md +1 -1
- package/CHANGELOG.md +10 -0
- package/docs/agent-fabric.md +174 -10
- package/package.json +1 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-fabric/index.ts +4 -0
- package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -0
- package/src/forge/agent-fabric/local-adaptive-harness.ts +343 -0
- package/src/forge/agent-fabric/local-adaptive-service.ts +398 -0
- package/src/forge/agent-fabric/local-adaptive-worker.ts +146 -0
- package/src/forge/agent-fabric/local-approval-window.ts +49 -7
- package/src/forge/agent-fabric/local-coding-worker.ts +107 -12
- package/src/forge/agent-fabric/local-effect-broker.ts +348 -0
- package/src/forge/agent-fabric/local-evolution-approval.ts +81 -0
- package/src/forge/agent-fabric/local-evolution-profile.ts +87 -0
- package/src/forge/agent-fabric/local-evolution-registry.ts +427 -0
- package/src/forge/agent-fabric/local-evolution-service.ts +200 -0
- package/src/forge/agent-fabric/local-intelligence.ts +281 -0
- package/src/forge/agent-fabric/local-task-contract.ts +63 -2
- package/src/forge/agent-fabric/local-task-inbox.ts +346 -2
- package/src/forge/agent-fabric/local-task-server.ts +49 -7
- package/src/forge/agent-fabric/local-task-service.ts +516 -28
- package/src/forge/agent-fabric/local-verification.ts +551 -0
- package/src/forge/agent-fabric/p0b-model-adapter.ts +37 -7
- package/src/forge/agent-memory/bridge.ts +156 -53
- package/src/forge/agent-memory/mcp.ts +16 -4
- package/src/forge/agent-memory/sources/codex-hook-runner.mjs +65 -3
- package/src/forge/cli/adaptive.ts +51 -0
- package/src/forge/cli/changed.ts +13 -32
- package/src/forge/cli/commands.ts +22 -4
- package/src/forge/cli/evolution.ts +40 -0
- package/src/forge/cli/fabric.ts +57 -9
- package/src/forge/cli/main.ts +17 -0
- package/src/forge/cli/parse.ts +45 -5
- package/src/forge/cli/studio.ts +141 -77
- package/src/forge/dev-console/cycle.ts +6 -1
- package/src/forge/review/index.ts +6 -2
- package/src/forge/version.ts +1 -1
- package/src/forge/workspace/change-summary.ts +37 -7
- package/src/forge/workspace/git-summary.ts +27 -25
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { digestCanonical, sha256Digest, stableStringify } from "./canonical.ts";
|
|
2
|
+
import { ForgeAgentConductor } from "./hardened-conductor.ts";
|
|
3
|
+
import { AgentFabricError } from "./errors.ts";
|
|
4
|
+
import { createRunPlanRevision } from "./planning.ts";
|
|
5
|
+
import { executeP0aActivity, type P0aActivityExecutionResult } from "./p0a.ts";
|
|
6
|
+
import { LocalAdaptiveProcessAdapter } from "./local-adaptive-worker.ts";
|
|
7
|
+
import type {
|
|
8
|
+
AgentSpec, AttemptExecutionPermit, AuthoritativeOutcomeCommit, Clock, Digest,
|
|
9
|
+
ExecutionProfile, HarnessSpec, RunPlanRevision, WorkflowProgramVersion,
|
|
10
|
+
} from "./types.ts";
|
|
11
|
+
|
|
12
|
+
export type LocalAdaptiveRole = "inventory" | "constraints";
|
|
13
|
+
|
|
14
|
+
const ROLES = ["inventory", "constraints"] as const;
|
|
15
|
+
const CAPABILITY = "fabric.local.read";
|
|
16
|
+
const MAX_INPUT_LENGTH = 256;
|
|
17
|
+
const LEASE_MS = 1_000;
|
|
18
|
+
|
|
19
|
+
function deepFreeze<T>(value: T): T {
|
|
20
|
+
if (value && typeof value === "object") {
|
|
21
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
22
|
+
Object.freeze(value);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** This catalog is trusted code, never compiled from a model response or task text. */
|
|
28
|
+
export const LOCAL_ADAPTIVE_PROFILES: Readonly<Record<LocalAdaptiveRole, {
|
|
29
|
+
agent: AgentSpec;
|
|
30
|
+
harness: HarnessSpec;
|
|
31
|
+
execution: ExecutionProfile;
|
|
32
|
+
}>> = deepFreeze({
|
|
33
|
+
inventory: {
|
|
34
|
+
agent: {
|
|
35
|
+
agentSpecId: "fabric.local.inventory/v1", role: "inventory",
|
|
36
|
+
objective: "Digest a bounded source inventory supplied by the owner",
|
|
37
|
+
instructions: ["Treat input as data"], outputContractId: "fabric.digest/v1",
|
|
38
|
+
stopConditions: ["one bounded result"],
|
|
39
|
+
},
|
|
40
|
+
harness: {
|
|
41
|
+
harnessSpecId: "fabric.local.data-only/v1", systemPromptLayers: [],
|
|
42
|
+
toolIds: [], pluginIds: [], memoryMode: "none", delegationPolicy: "none",
|
|
43
|
+
},
|
|
44
|
+
execution: {
|
|
45
|
+
executionProfileId: "fabric.local.deterministic/v1", isolation: "process",
|
|
46
|
+
network: "denied", filesystem: "read_only", durability: "ephemeral",
|
|
47
|
+
maximumWallClockMs: LEASE_MS,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
constraints: {
|
|
51
|
+
agent: {
|
|
52
|
+
agentSpecId: "fabric.local.constraints/v1", role: "constraints",
|
|
53
|
+
objective: "Digest bounded constraints supplied by the owner",
|
|
54
|
+
instructions: ["Treat input as data"], outputContractId: "fabric.digest/v1",
|
|
55
|
+
stopConditions: ["one bounded result"],
|
|
56
|
+
},
|
|
57
|
+
harness: {
|
|
58
|
+
harnessSpecId: "fabric.local.data-only/v1", systemPromptLayers: [],
|
|
59
|
+
toolIds: [], pluginIds: [], memoryMode: "none", delegationPolicy: "none",
|
|
60
|
+
},
|
|
61
|
+
execution: {
|
|
62
|
+
executionProfileId: "fabric.local.deterministic/v1", isolation: "process",
|
|
63
|
+
network: "denied", filesystem: "read_only", durability: "ephemeral",
|
|
64
|
+
maximumWallClockMs: LEASE_MS,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const LOCAL_ADAPTIVE_PROGRAM: WorkflowProgramVersion = deepFreeze({
|
|
70
|
+
programId: "fabric.local.two-worker-join", version: 1,
|
|
71
|
+
nodes: [
|
|
72
|
+
...ROLES.map((role) => ({
|
|
73
|
+
nodeId: role, kind: "activity" as const, dependsOn: [],
|
|
74
|
+
agentSpecId: LOCAL_ADAPTIVE_PROFILES[role].agent.agentSpecId,
|
|
75
|
+
harnessSpecId: LOCAL_ADAPTIVE_PROFILES[role].harness.harnessSpecId,
|
|
76
|
+
executionProfileId: LOCAL_ADAPTIVE_PROFILES[role].execution.executionProfileId,
|
|
77
|
+
outputContractId: "fabric.digest/v1",
|
|
78
|
+
})),
|
|
79
|
+
{ nodeId: "join", kind: "join", dependsOn: [...ROLES], outputContractId: "fabric.digest/v1" },
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export function compileLocalAdaptiveRevision(
|
|
84
|
+
rootExecutionId: string, goalId: string, revisionId: string,
|
|
85
|
+
): RunPlanRevision {
|
|
86
|
+
return createRunPlanRevision(
|
|
87
|
+
rootExecutionId, goalId, LOCAL_ADAPTIVE_PROGRAM, revisionId, sha256Digest,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface LocalAdaptiveHarnessInput {
|
|
92
|
+
conductor: ForgeAgentConductor;
|
|
93
|
+
clock: Clock;
|
|
94
|
+
rootExecutionId: string;
|
|
95
|
+
revisionId: string;
|
|
96
|
+
parentGrantId: string;
|
|
97
|
+
sourceId: string;
|
|
98
|
+
targetId: string;
|
|
99
|
+
inventory: string;
|
|
100
|
+
constraints: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type LocalAdaptiveJoinResult =
|
|
104
|
+
| { status: "blocked"; reason: "incomplete_or_failed_child" | "unexpected_child_result" }
|
|
105
|
+
| { status: "succeeded"; outcome: AuthoritativeOutcomeCommit };
|
|
106
|
+
|
|
107
|
+
export interface LocalAdaptiveProcessResult {
|
|
108
|
+
children: Readonly<Record<LocalAdaptiveRole,
|
|
109
|
+
P0aActivityExecutionResult | { status: "error"; reason: string }>>;
|
|
110
|
+
workerPids: Readonly<Partial<Record<LocalAdaptiveRole, number>>>;
|
|
111
|
+
join: LocalAdaptiveJoinResult;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A fixed two-process data workflow with a deterministic in-process test seam.
|
|
116
|
+
* The workers digest supplied text only; they do not run arbitrary task code.
|
|
117
|
+
*/
|
|
118
|
+
export class LocalAdaptiveHarness {
|
|
119
|
+
private readonly permits: Partial<Record<LocalAdaptiveRole, AttemptExecutionPermit>> = {};
|
|
120
|
+
private prepared = false;
|
|
121
|
+
private processRunStarted = false;
|
|
122
|
+
private readonly inputs: Record<LocalAdaptiveRole, string>;
|
|
123
|
+
|
|
124
|
+
constructor(private readonly config: LocalAdaptiveHarnessInput) {
|
|
125
|
+
this.inputs = { inventory: config.inventory, constraints: config.constraints };
|
|
126
|
+
for (const role of ROLES) {
|
|
127
|
+
const value = this.inputs[role];
|
|
128
|
+
if (typeof value !== "string" || value.length > MAX_INPUT_LENGTH) {
|
|
129
|
+
throw new AgentFabricError("AF_INVALID_STATE", `${role} must be bounded text data`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
prepare(): Readonly<Record<LocalAdaptiveRole, AttemptExecutionPermit>> {
|
|
135
|
+
if (this.prepared) throw new AgentFabricError("AF_CONFLICT", "Harness already prepared");
|
|
136
|
+
const { conductor, clock, rootExecutionId, revisionId, parentGrantId, sourceId, targetId } = this.config;
|
|
137
|
+
const state = conductor.state();
|
|
138
|
+
const revision = state.planRevisions[revisionId];
|
|
139
|
+
const expected = revision && compileLocalAdaptiveRevision(rootExecutionId, revision.goalId, revisionId);
|
|
140
|
+
if (
|
|
141
|
+
state.activePlanRevisionByExecution[rootExecutionId] !== revisionId ||
|
|
142
|
+
!revision || !expected || stableStringify(revision) !== stableStringify(expected)
|
|
143
|
+
) {
|
|
144
|
+
throw new AgentFabricError("AF_INVALID_PLAN", "Fixed local harness requires its active exact plan");
|
|
145
|
+
}
|
|
146
|
+
const parent = state.grants[parentGrantId];
|
|
147
|
+
const workerResource = state.resourceDefinitions.workers;
|
|
148
|
+
const capacityRemaining = workerResource && workerResource.limit -
|
|
149
|
+
(state.resourceReserved.workers ?? 0) - (state.resourceConsumed.workers ?? 0);
|
|
150
|
+
const parentIssued = Object.values(state.permits).filter((permit) => permit.grantId === parentGrantId).length;
|
|
151
|
+
const parentDelegated = Object.values(state.grants)
|
|
152
|
+
.filter((grant) => grant.parentGrantId === parentGrantId)
|
|
153
|
+
.reduce((total, grant) => total + grant.maximumAttempts, 0);
|
|
154
|
+
if (
|
|
155
|
+
!parent || parent.subjectId !== "fabric.local.coordinator" ||
|
|
156
|
+
parent.maximumAttempts - parentIssued - parentDelegated < 3 ||
|
|
157
|
+
parent.delegationDepthRemaining < 1 ||
|
|
158
|
+
(parent.resourceCeilings.workers ?? 0) < 2 ||
|
|
159
|
+
(workerResource && (workerResource.semantics !== "capacity" || capacityRemaining! < 2)) ||
|
|
160
|
+
!parent.capabilities.includes(CAPABILITY) ||
|
|
161
|
+
!parent.sourceIds.includes(sourceId) || !parent.targetIds.includes(targetId) ||
|
|
162
|
+
!parent.effectClasses.includes("read") || parent.expiresAt <= clock.now() + LEASE_MS
|
|
163
|
+
) {
|
|
164
|
+
throw new AgentFabricError("AF_GRANT_REJECTED", "Parent grant cannot fund the fixed local workflow");
|
|
165
|
+
}
|
|
166
|
+
for (const role of ROLES) {
|
|
167
|
+
const workerId = this.workerId(role);
|
|
168
|
+
const resolution = conductor.deriveAndRegisterGrant(parentGrantId, {
|
|
169
|
+
grantId: this.grantId(role), subjectId: workerId,
|
|
170
|
+
capabilities: [CAPABILITY], sourceIds: [sourceId], targetIds: [targetId],
|
|
171
|
+
effectClasses: ["read"], notBefore: clock.now(),
|
|
172
|
+
expiresAt: Math.min(parent.expiresAt, clock.now() + 5_000),
|
|
173
|
+
maximumAttempts: 1, delegationDepthRemaining: 0,
|
|
174
|
+
reservationId: this.reservationId(role),
|
|
175
|
+
resourceRequests: [{ resource: "workers", amount: 1 }],
|
|
176
|
+
});
|
|
177
|
+
if (resolution.outcome !== "allowed") {
|
|
178
|
+
throw new AgentFabricError("AF_RESOURCE_EXHAUSTED", `Child ${role} rejected: ${resolution.reasonCodes.join(",")}`);
|
|
179
|
+
}
|
|
180
|
+
const intentId = this.intentId(role);
|
|
181
|
+
conductor.commitDispatchIntent({
|
|
182
|
+
intentId, rootExecutionId, planRevisionId: revisionId, taskNodeId: role,
|
|
183
|
+
effectiveRunSpecDigest: this.specDigest(role), sourceIds: [sourceId], targetId,
|
|
184
|
+
requiredCapability: CAPABILITY, effectClass: "read", createdAt: clock.now(),
|
|
185
|
+
});
|
|
186
|
+
const claim = conductor.claimDispatch({
|
|
187
|
+
claimId: this.claimId(role), intentId, workerId,
|
|
188
|
+
attemptId: this.attemptId(role), leaseDurationMs: LEASE_MS,
|
|
189
|
+
});
|
|
190
|
+
this.permits[role] = conductor.issuePermit({
|
|
191
|
+
permitId: this.permitId(role), claimId: claim.claimId,
|
|
192
|
+
grantId: this.grantId(role), maximumValidityMs: LEASE_MS,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
this.prepared = true;
|
|
196
|
+
return { inventory: this.requirePermit("inventory"), constraints: this.requirePermit("constraints") };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Execute both permitted roles in separate bounded Node processes and join
|
|
200
|
+
* only committed, parent-verified child digests. */
|
|
201
|
+
async run(signal?: AbortSignal): Promise<LocalAdaptiveProcessResult> {
|
|
202
|
+
if (signal?.aborted) throw new AgentFabricError("AF_INVALID_STATE", "Process workflow cancelled before prepare");
|
|
203
|
+
if (!this.prepared) this.prepare();
|
|
204
|
+
return this.runPrepared(signal);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Rebind only the two exact permits already committed in a durable journal. */
|
|
208
|
+
resumePrepared(): void {
|
|
209
|
+
if (this.prepared || this.processRunStarted) throw new AgentFabricError("AF_CONFLICT", "Harness already prepared");
|
|
210
|
+
const state = this.config.conductor.state();
|
|
211
|
+
const revision = state.planRevisions[this.config.revisionId];
|
|
212
|
+
if (!revision || state.activePlanRevisionByExecution[this.config.rootExecutionId] !== this.config.revisionId ||
|
|
213
|
+
stableStringify(revision) !== stableStringify(compileLocalAdaptiveRevision(
|
|
214
|
+
this.config.rootExecutionId, revision.goalId, this.config.revisionId))) {
|
|
215
|
+
throw new AgentFabricError("AF_INVALID_PLAN", "Fixed local harness requires its active exact plan");
|
|
216
|
+
}
|
|
217
|
+
for (const role of ROLES) {
|
|
218
|
+
const permit = state.permits[this.permitId(role)];
|
|
219
|
+
if (!permit || permit.attemptId !== this.attemptId(role) ||
|
|
220
|
+
permit.intentId !== this.intentId(role) || permit.grantId !== this.grantId(role) ||
|
|
221
|
+
permit.effectiveRunSpecDigest !== this.specDigest(role) ||
|
|
222
|
+
state.outcomes[permit.attemptId]) {
|
|
223
|
+
throw new AgentFabricError("AF_INVALID_STATE", `Missing or already used ${role} permit`);
|
|
224
|
+
}
|
|
225
|
+
this.permits[role] = permit;
|
|
226
|
+
}
|
|
227
|
+
this.prepared = true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Execute a previously committed pair of permits exactly once. */
|
|
231
|
+
async runPrepared(signal?: AbortSignal): Promise<LocalAdaptiveProcessResult> {
|
|
232
|
+
if (!this.prepared || this.processRunStarted) throw new AgentFabricError("AF_CONFLICT", "Process workflow is not ready");
|
|
233
|
+
if (signal?.aborted) throw new AgentFabricError("AF_INVALID_STATE", "Process workflow cancelled before start");
|
|
234
|
+
const permits = { inventory: this.requirePermit("inventory"), constraints: this.requirePermit("constraints") };
|
|
235
|
+
this.processRunStarted = true;
|
|
236
|
+
const adapters = {
|
|
237
|
+
inventory: new LocalAdaptiveProcessAdapter("inventory", this.inputs.inventory, this.config.clock, signal),
|
|
238
|
+
constraints: new LocalAdaptiveProcessAdapter("constraints", this.inputs.constraints, this.config.clock, signal),
|
|
239
|
+
};
|
|
240
|
+
const settled = await Promise.allSettled(ROLES.map((role) =>
|
|
241
|
+
executeP0aActivity({ conductor: this.config.conductor, adapter: adapters[role], permit: permits[role] })));
|
|
242
|
+
const children = {} as Record<LocalAdaptiveRole, LocalAdaptiveProcessResult["children"][LocalAdaptiveRole]>;
|
|
243
|
+
for (const [index, role] of ROLES.entries()) {
|
|
244
|
+
const result = settled[index]!;
|
|
245
|
+
children[role] = result.status === "fulfilled" ? result.value :
|
|
246
|
+
{ status: "error", reason: result.reason instanceof Error ? result.reason.message : "activity_rejected" };
|
|
247
|
+
}
|
|
248
|
+
return { children,
|
|
249
|
+
workerPids: { inventory: adapters.inventory.pid, constraints: adapters.constraints.pid },
|
|
250
|
+
join: this.join() };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Runs only a fixed data digest. Failure can narrow authority, never expand it. */
|
|
254
|
+
complete(role: LocalAdaptiveRole, fail = false): AuthoritativeOutcomeCommit {
|
|
255
|
+
if (!ROLES.includes(role)) throw new AgentFabricError("AF_INVALID_STATE", "Unknown fixed worker role");
|
|
256
|
+
const permit = this.requirePermit(role);
|
|
257
|
+
const { conductor, clock } = this.config;
|
|
258
|
+
conductor.authorizeAttemptDispatch(permit);
|
|
259
|
+
conductor.acceptStartupReport(permit, {
|
|
260
|
+
startupReportId: `startup:${permit.attemptId}`,
|
|
261
|
+
attemptId: permit.attemptId, observedSpecDigest: permit.effectiveRunSpecDigest,
|
|
262
|
+
startedAt: clock.now(),
|
|
263
|
+
});
|
|
264
|
+
return conductor.commitOutcome({
|
|
265
|
+
reportId: `report:${permit.attemptId}`, attemptId: permit.attemptId,
|
|
266
|
+
permitId: permit.permitId, intentId: permit.intentId,
|
|
267
|
+
planRevisionId: permit.planRevisionId,
|
|
268
|
+
effectiveRunSpecDigest: permit.effectiveRunSpecDigest,
|
|
269
|
+
fencingToken: permit.fencingToken, status: fail ? "failed" : "succeeded",
|
|
270
|
+
resultDigest: this.resultDigest(role), evidenceDigests: [], reportedAt: clock.now(),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
join(): LocalAdaptiveJoinResult {
|
|
275
|
+
if (!this.prepared) throw new AgentFabricError("AF_INVALID_STATE", "Harness is not prepared");
|
|
276
|
+
const { conductor, clock, rootExecutionId, revisionId, sourceId, targetId, parentGrantId } = this.config;
|
|
277
|
+
const state = conductor.state();
|
|
278
|
+
const children = ROLES.map((role) => state.outcomes[this.attemptId(role)]);
|
|
279
|
+
if (children.some((outcome) => !outcome || outcome.status !== "succeeded")) {
|
|
280
|
+
return { status: "blocked", reason: "incomplete_or_failed_child" };
|
|
281
|
+
}
|
|
282
|
+
if (ROLES.some((role, index) => {
|
|
283
|
+
const outcome = children[index]!;
|
|
284
|
+
return outcome.intentId !== this.intentId(role) || outcome.permitId !== this.permitId(role) ||
|
|
285
|
+
outcome.resultDigest !== this.resultDigest(role);
|
|
286
|
+
})) return { status: "blocked", reason: "unexpected_child_result" };
|
|
287
|
+
|
|
288
|
+
const intentId = this.intentId("join");
|
|
289
|
+
const resultDigest = digestCanonical({
|
|
290
|
+
inventory: children[0]!.resultDigest, constraints: children[1]!.resultDigest,
|
|
291
|
+
revisionId,
|
|
292
|
+
}, sha256Digest);
|
|
293
|
+
conductor.commitDispatchIntent({
|
|
294
|
+
intentId, rootExecutionId, planRevisionId: revisionId, taskNodeId: "join",
|
|
295
|
+
effectiveRunSpecDigest: digestCanonical({ kind: "fixed_join", revisionId }, sha256Digest),
|
|
296
|
+
sourceIds: [sourceId], targetId, requiredCapability: CAPABILITY,
|
|
297
|
+
effectClass: "read", createdAt: clock.now(),
|
|
298
|
+
});
|
|
299
|
+
const claim = conductor.claimDispatch({
|
|
300
|
+
claimId: this.claimId("join"), intentId, workerId: "fabric.local.coordinator",
|
|
301
|
+
attemptId: this.attemptId("join"), leaseDurationMs: LEASE_MS,
|
|
302
|
+
});
|
|
303
|
+
const permit = conductor.issuePermit({
|
|
304
|
+
permitId: this.permitId("join"), claimId: claim.claimId,
|
|
305
|
+
grantId: parentGrantId, maximumValidityMs: LEASE_MS,
|
|
306
|
+
});
|
|
307
|
+
conductor.authorizeAttemptDispatch(permit);
|
|
308
|
+
conductor.acceptStartupReport(permit, {
|
|
309
|
+
startupReportId: `startup:${permit.attemptId}`, attemptId: permit.attemptId,
|
|
310
|
+
observedSpecDigest: permit.effectiveRunSpecDigest, startedAt: clock.now(),
|
|
311
|
+
});
|
|
312
|
+
const outcome = conductor.commitOutcome({
|
|
313
|
+
reportId: `report:${permit.attemptId}`, attemptId: permit.attemptId,
|
|
314
|
+
permitId: permit.permitId, intentId, planRevisionId: revisionId,
|
|
315
|
+
effectiveRunSpecDigest: permit.effectiveRunSpecDigest,
|
|
316
|
+
fencingToken: permit.fencingToken, status: "succeeded", resultDigest,
|
|
317
|
+
evidenceDigests: children.map((child) => child!.reportDigest), reportedAt: clock.now(),
|
|
318
|
+
});
|
|
319
|
+
return { status: "succeeded", outcome };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private specDigest(role: LocalAdaptiveRole): Digest {
|
|
323
|
+
return digestCanonical({
|
|
324
|
+
profile: LOCAL_ADAPTIVE_PROFILES[role], input: this.inputs[role],
|
|
325
|
+
revisionId: this.config.revisionId,
|
|
326
|
+
}, sha256Digest);
|
|
327
|
+
}
|
|
328
|
+
private resultDigest(role: LocalAdaptiveRole): Digest {
|
|
329
|
+
return digestCanonical({ role, input: this.inputs[role] }, sha256Digest);
|
|
330
|
+
}
|
|
331
|
+
private workerId(role: LocalAdaptiveRole): string { return `fabric.local.${role}`; }
|
|
332
|
+
private grantId(role: LocalAdaptiveRole): string { return `grant:${this.config.revisionId}:${role}`; }
|
|
333
|
+
private reservationId(role: LocalAdaptiveRole): string { return `reservation:${this.config.revisionId}:${role}`; }
|
|
334
|
+
private intentId(role: LocalAdaptiveRole | "join"): string { return `intent:${this.config.revisionId}:${role}`; }
|
|
335
|
+
private claimId(role: LocalAdaptiveRole | "join"): string { return `claim:${this.config.revisionId}:${role}`; }
|
|
336
|
+
private attemptId(role: LocalAdaptiveRole | "join"): string { return `attempt:${this.config.revisionId}:${role}`; }
|
|
337
|
+
private permitId(role: LocalAdaptiveRole | "join"): string { return `permit:${this.config.revisionId}:${role}`; }
|
|
338
|
+
private requirePermit(role: LocalAdaptiveRole): AttemptExecutionPermit {
|
|
339
|
+
const permit = this.permits[role];
|
|
340
|
+
if (!permit) throw new AgentFabricError("AF_INVALID_STATE", `Worker ${role} is not prepared`);
|
|
341
|
+
return permit;
|
|
342
|
+
}
|
|
343
|
+
}
|