pi-daddy 0.43.0 → 0.44.0
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/CHANGELOG.md +104 -33
- package/README.md +27 -5
- package/THIRD_PARTY_LICENSES/musl-COPYRIGHT.txt +193 -0
- package/THIRD_PARTY_LICENSES/zig-MIT.txt +21 -0
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/contracts/ledger-record/v1/README.md +40 -25
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -5
- package/dist/cli.js.map +1 -1
- package/dist/executors/activity-session.js +116 -65
- package/dist/executors/activity-session.js.map +1 -1
- package/dist/executors/captured-execution.d.ts +13 -0
- package/dist/executors/captured-execution.d.ts.map +1 -0
- package/dist/executors/captured-execution.js +53 -0
- package/dist/executors/captured-execution.js.map +1 -0
- package/dist/executors/child-final.d.ts +31 -0
- package/dist/executors/child-final.d.ts.map +1 -0
- package/dist/executors/child-final.js +197 -0
- package/dist/executors/child-final.js.map +1 -0
- package/dist/executors/executor.d.ts +2 -1
- package/dist/executors/executor.d.ts.map +1 -1
- package/dist/executors/executor.js +12 -0
- package/dist/executors/executor.js.map +1 -1
- package/dist/executors/owned-worker.d.ts +18 -0
- package/dist/executors/owned-worker.d.ts.map +1 -0
- package/dist/executors/owned-worker.js +295 -0
- package/dist/executors/owned-worker.js.map +1 -0
- package/dist/executors/worker-artifact.d.ts +3 -0
- package/dist/executors/worker-artifact.d.ts.map +1 -0
- package/dist/executors/worker-artifact.js +3 -0
- package/dist/executors/worker-artifact.js.map +1 -0
- package/dist/governance/approval-prompt.d.ts +4 -3
- package/dist/governance/approval-prompt.d.ts.map +1 -1
- package/dist/governance/approval-prompt.js +13 -6
- package/dist/governance/approval-prompt.js.map +1 -1
- package/dist/governance/captured-worker-record.d.ts +9 -0
- package/dist/governance/captured-worker-record.d.ts.map +1 -0
- package/dist/governance/captured-worker-record.js +123 -0
- package/dist/governance/captured-worker-record.js.map +1 -0
- package/dist/governance/lease-record.d.ts +4 -0
- package/dist/governance/lease-record.d.ts.map +1 -1
- package/dist/governance/lease-record.js +19 -2
- package/dist/governance/lease-record.js.map +1 -1
- package/dist/governance/ledger-events.d.ts +5 -5
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +1 -1
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/workspace-acceptance.d.ts +3 -1
- package/dist/governance/workspace-acceptance.d.ts.map +1 -1
- package/dist/governance/workspace-acceptance.js +21 -5
- package/dist/governance/workspace-acceptance.js.map +1 -1
- package/dist/governance/workspace-lease.d.ts.map +1 -1
- package/dist/governance/workspace-lease.js +44 -1
- package/dist/governance/workspace-lease.js.map +1 -1
- package/dist/kernel/authority-text.d.ts +3 -0
- package/dist/kernel/authority-text.d.ts.map +1 -0
- package/dist/kernel/authority-text.js +22 -0
- package/dist/kernel/authority-text.js.map +1 -0
- package/dist/kernel/bounded-read-failures.d.ts +9 -0
- package/dist/kernel/bounded-read-failures.d.ts.map +1 -0
- package/dist/kernel/bounded-read-failures.js +33 -0
- package/dist/kernel/bounded-read-failures.js.map +1 -0
- package/dist/kernel/bounded-read-sync.d.ts +24 -0
- package/dist/kernel/bounded-read-sync.d.ts.map +1 -0
- package/dist/kernel/bounded-read-sync.js +75 -0
- package/dist/kernel/bounded-read-sync.js.map +1 -0
- package/dist/kernel/bounded-read.d.ts +29 -2
- package/dist/kernel/bounded-read.d.ts.map +1 -1
- package/dist/kernel/bounded-read.js +98 -9
- package/dist/kernel/bounded-read.js.map +1 -1
- package/dist/kernel/capacity.d.ts +36 -0
- package/dist/kernel/capacity.d.ts.map +1 -0
- package/dist/kernel/capacity.js +94 -0
- package/dist/kernel/capacity.js.map +1 -0
- package/dist/kernel/captured-worker-contract.d.ts +38 -0
- package/dist/kernel/captured-worker-contract.d.ts.map +1 -0
- package/dist/kernel/captured-worker-contract.js +2 -0
- package/dist/kernel/captured-worker-contract.js.map +1 -0
- package/dist/kernel/catalog.d.ts +12 -3
- package/dist/kernel/catalog.d.ts.map +1 -1
- package/dist/kernel/catalog.js +28 -8
- package/dist/kernel/catalog.js.map +1 -1
- package/dist/kernel/chain.d.ts +1 -0
- package/dist/kernel/chain.d.ts.map +1 -1
- package/dist/kernel/chain.js.map +1 -1
- package/dist/kernel/definitions.d.ts +11 -1
- package/dist/kernel/definitions.d.ts.map +1 -1
- package/dist/kernel/definitions.js +6 -3
- package/dist/kernel/definitions.js.map +1 -1
- package/dist/kernel/fanout.d.ts +5 -25
- package/dist/kernel/fanout.d.ts.map +1 -1
- package/dist/kernel/fanout.js +15 -30
- package/dist/kernel/fanout.js.map +1 -1
- package/dist/kernel/grant-env.d.ts +0 -2
- package/dist/kernel/grant-env.d.ts.map +1 -1
- package/dist/kernel/grant-env.js +0 -1
- package/dist/kernel/grant-env.js.map +1 -1
- package/dist/kernel/model-preflight.d.ts +8 -0
- package/dist/kernel/model-preflight.d.ts.map +1 -1
- package/dist/kernel/model-preflight.js +23 -0
- package/dist/kernel/model-preflight.js.map +1 -1
- package/dist/kernel/selected-definitions.d.ts +15 -0
- package/dist/kernel/selected-definitions.d.ts.map +1 -0
- package/dist/kernel/selected-definitions.js +146 -0
- package/dist/kernel/selected-definitions.js.map +1 -0
- package/dist/kernel/skill-packages.d.ts +4 -2
- package/dist/kernel/skill-packages.d.ts.map +1 -1
- package/dist/kernel/skill-packages.js +34 -41
- package/dist/kernel/skill-packages.js.map +1 -1
- package/dist/kernel/skill-resources.d.ts.map +1 -1
- package/dist/kernel/skill-resources.js +8 -11
- package/dist/kernel/skill-resources.js.map +1 -1
- package/dist/kernel/workspace.d.ts +6 -3
- package/dist/kernel/workspace.d.ts.map +1 -1
- package/dist/kernel/workspace.js +10 -6
- package/dist/kernel/workspace.js.map +1 -1
- package/dist/products/dashboard-cli.js +1 -1
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-render.d.ts +1 -2
- package/dist/products/dashboard-render.d.ts.map +1 -1
- package/dist/products/dashboard-render.js +3 -1
- package/dist/products/dashboard-render.js.map +1 -1
- package/dist/products/dashboard-session-client.d.ts +1 -2
- package/dist/products/dashboard-session-client.d.ts.map +1 -1
- package/dist/products/dashboard-session-client.js.map +1 -1
- package/dist/products/episode-report.d.ts +12 -8
- package/dist/products/episode-report.d.ts.map +1 -1
- package/dist/products/episode-report.js +61 -12
- package/dist/products/episode-report.js.map +1 -1
- package/extensions/approvals.ts +1 -1
- package/extensions/chain-plan.ts +9 -3
- package/extensions/context-staging.ts +30 -35
- package/extensions/dashboard-session-server.ts +1 -2
- package/extensions/definition-describe.ts +44 -0
- package/extensions/definition-runtime.ts +89 -26
- package/extensions/delegate-chain.ts +273 -256
- package/extensions/delegation-activation.ts +1 -1
- package/extensions/delegation.ts +41 -53
- package/extensions/execute-child.ts +167 -134
- package/extensions/fanout-outcome.ts +1 -9
- package/extensions/grants-command.ts +2 -9
- package/extensions/grants.ts +106 -38
- package/extensions/init-command.ts +52 -12
- package/extensions/reload-environment.ts +21 -2
- package/extensions/retired-inputs.ts +7 -0
- package/extensions/run-delegation.ts +212 -265
- package/extensions/session-capacity.ts +53 -0
- package/extensions/session-environment.ts +5 -2
- package/extensions/session-model-prompt.ts +33 -32
- package/extensions/session-report.ts +28 -11
- package/extensions/session.ts +200 -157
- package/extensions/workspace-runtime.ts +2 -1
- package/native/README.md +25 -0
- package/native/linux-x64/worker +0 -0
- package/native/linux-x64/worker.sha256 +1 -0
- package/native/provenance.json +42 -0
- package/native/toolchain.json +18 -0
- package/native/worker.c +209 -0
- package/package.json +15 -7
- package/src/cli.ts +16 -6
- package/src/executors/activity-session.ts +120 -66
- package/src/executors/captured-execution.ts +61 -0
- package/src/executors/child-final.ts +190 -0
- package/src/executors/executor.ts +14 -1
- package/src/executors/owned-worker.ts +322 -0
- package/src/executors/worker-artifact.ts +2 -0
- package/src/governance/approval-prompt.ts +18 -8
- package/src/governance/captured-worker-record.ts +138 -0
- package/src/governance/lease-record.ts +21 -2
- package/src/governance/ledger-events.ts +5 -5
- package/src/governance/ledger-v3-validation.ts +1 -1
- package/src/governance/workspace-acceptance.ts +20 -4
- package/src/governance/workspace-lease.ts +59 -1
- package/src/kernel/authority-text.ts +17 -0
- package/src/kernel/bounded-read-failures.ts +42 -0
- package/src/kernel/bounded-read-sync.ts +87 -0
- package/src/kernel/bounded-read.ts +128 -11
- package/src/kernel/capacity.ts +123 -0
- package/src/kernel/captured-worker-contract.ts +29 -0
- package/src/kernel/catalog.ts +45 -16
- package/src/kernel/chain.ts +1 -0
- package/src/kernel/definitions.ts +11 -2
- package/src/kernel/fanout.ts +17 -32
- package/src/kernel/grant-env.ts +0 -3
- package/src/kernel/model-preflight.ts +29 -0
- package/src/kernel/selected-definitions.ts +156 -0
- package/src/kernel/skill-packages.ts +40 -38
- package/src/kernel/skill-resources.ts +8 -9
- package/src/kernel/workspace.ts +12 -5
- package/src/products/dashboard-cli.ts +1 -1
- package/src/products/dashboard-render.ts +6 -2
- package/src/products/dashboard-session-client.ts +1 -2
- package/src/products/episode-report.ts +80 -17
- package/dist/advisors/advisor.d.ts +0 -90
- package/dist/advisors/advisor.d.ts.map +0 -1
- package/dist/advisors/advisor.js +0 -154
- package/dist/advisors/advisor.js.map +0 -1
- package/dist/advisors/decider.d.ts +0 -88
- package/dist/advisors/decider.d.ts.map +0 -1
- package/dist/advisors/decider.js +0 -29
- package/dist/advisors/decider.js.map +0 -1
- package/dist/advisors/jev.d.ts +0 -41
- package/dist/advisors/jev.d.ts.map +0 -1
- package/dist/advisors/jev.js +0 -139
- package/dist/advisors/jev.js.map +0 -1
- package/dist/advisors/settings.d.ts +0 -49
- package/dist/advisors/settings.d.ts.map +0 -1
- package/dist/advisors/settings.js +0 -99
- package/dist/advisors/settings.js.map +0 -1
- package/dist/governance/episode-cost-gate.d.ts +0 -31
- package/dist/governance/episode-cost-gate.d.ts.map +0 -1
- package/dist/governance/episode-cost-gate.js +0 -112
- package/dist/governance/episode-cost-gate.js.map +0 -1
- package/extensions/advisor-session.ts +0 -67
- package/extensions/effort-advice.ts +0 -112
- package/extensions/pruning-advice.ts +0 -133
- package/src/advisors/advisor.ts +0 -246
- package/src/advisors/decider.ts +0 -74
- package/src/advisors/jev.ts +0 -163
- package/src/advisors/settings.ts +0 -115
- package/src/governance/episode-cost-gate.ts +0 -128
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { reconcileDelegationCapacity, reserveDelegationCapacity } from "./session-capacity.ts";
|
|
2
|
+
import type { CapacityReservation } from "../src/kernel/capacity.ts";
|
|
3
|
+
import { assertDefinitionIdentity } from "./definition-describe.ts";
|
|
1
4
|
/**
|
|
2
5
|
* Plan, gate, audit and run ONE governed child — the whole of a delegation except its tool surface.
|
|
3
6
|
*
|
|
@@ -12,8 +15,6 @@
|
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import { nativeDelegationContext } from "./delegation-native.ts";
|
|
15
|
-
import { adviseEffort } from "./effort-advice.ts";
|
|
16
|
-
import { advisePruning } from "./pruning-advice.ts";
|
|
17
18
|
import { DELEGATE_SUBJECT, shouldSeekApproval } from "../src/kernel/approval.ts";
|
|
18
19
|
import { planDelegation } from "../src/kernel/delegate.ts";
|
|
19
20
|
import {
|
|
@@ -27,13 +28,12 @@ import {
|
|
|
27
28
|
import type { InheritableApproval } from "../src/kernel/approval.ts";
|
|
28
29
|
import type { GrantsSession } from "./session.ts";
|
|
29
30
|
import type { CorrelationMetadata } from "../src/kernel/correlation.ts";
|
|
30
|
-
import { preflightModel, type ModelCatalogue } from "../src/kernel/model-preflight.ts";
|
|
31
|
+
import { preflightModel, runtimePairUnavailable, type ModelCatalogue } from "../src/kernel/model-preflight.ts";
|
|
31
32
|
import { GovernanceRefusal, refusal as structuredRefusal } from "../src/kernel/refusals.ts";
|
|
32
33
|
import { executePlannedChild, type DelegationOutcome } from "./execute-child.ts";
|
|
33
34
|
import { recordDelegationDecision, type ApprovalLedgerFacts } from "./delegation-ledger.ts";
|
|
34
35
|
import type { ExecutionOccurrenceIds } from "./execution-occurrence.ts";
|
|
35
|
-
import {
|
|
36
|
-
import { resolvedModelOf, resolveDefinitionRuntime } from "./definition-runtime.ts";
|
|
36
|
+
import { resolveDefinitionRuntime } from "./definition-runtime.ts";
|
|
37
37
|
import {
|
|
38
38
|
governedWorkspaceAccess,
|
|
39
39
|
prepareDelegationWorkspace,
|
|
@@ -44,13 +44,12 @@ import {
|
|
|
44
44
|
|
|
45
45
|
/** What one child was asked to do. The shape both tools accept, per child. */
|
|
46
46
|
interface ChildSpec {
|
|
47
|
+
definitionId?: string;
|
|
47
48
|
task: string;
|
|
48
49
|
agent?: string;
|
|
49
50
|
tools?: string[];
|
|
50
51
|
model?: string;
|
|
51
52
|
thinking?: string;
|
|
52
|
-
/** Positive USD override for the whole episode, applied only when this delegation will run. */
|
|
53
|
-
episodeCostCeiling?: number;
|
|
54
53
|
/** ADR-0078: what of the parent's session crosses. Validated in the kernel, never here. */
|
|
55
54
|
context?: unknown;
|
|
56
55
|
correlation?: CorrelationMetadata;
|
|
@@ -209,6 +208,10 @@ export async function runOneDelegation(
|
|
|
209
208
|
* mistake unspellable.
|
|
210
209
|
*/
|
|
211
210
|
options: {
|
|
211
|
+
/** Trusted reservation made before a chain's upfront gate. */
|
|
212
|
+
capacityReservation?: CapacityReservation;
|
|
213
|
+
/** Exact pair selected during the chain preflight; never reselect after its approval. */
|
|
214
|
+
resolvedRuntime?: import("./definition-runtime.ts").ResolvedDefinitionRuntime;
|
|
212
215
|
/** Actual public execute argument, never read from model-authored correlation or output. */
|
|
213
216
|
toolCallId?: string;
|
|
214
217
|
/** Progress for the parent's status block (ADR-0032). Display only. */
|
|
@@ -246,282 +249,226 @@ export async function runOneDelegation(
|
|
|
246
249
|
approvalFacts?: ApprovalLedgerFacts;
|
|
247
250
|
} = {},
|
|
248
251
|
): Promise<DelegationOutcome> {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// `ctx: null` rather than skipping the plan entirely: the ledger still gets a full, honest record of what was
|
|
295
|
-
// requested and refused, and stored approvals still count toward it — nothing is *hidden*, only nobody is
|
|
296
|
-
// *asked*. It is the same argument `/grants` uses for its preview.
|
|
297
|
-
let executorRefusal = session.executor.refusal;
|
|
298
|
-
const modelRefusal = preflightModel(
|
|
299
|
-
request.model,
|
|
300
|
-
ctx.modelRegistry,
|
|
301
|
-
session.modelResolutionCache,
|
|
302
|
-
session.allowUnresolvedModels,
|
|
303
|
-
);
|
|
304
|
-
const { extra, refusal: nativeRefusal } = await nativeDelegationContext(
|
|
305
|
-
session,
|
|
306
|
-
ids,
|
|
307
|
-
budget,
|
|
308
|
-
Boolean(executorRefusal || modelRefusal),
|
|
309
|
-
);
|
|
310
|
-
executorRefusal ||= nativeRefusal;
|
|
311
|
-
|
|
312
|
-
// ADR-0077's first decision point, after the refusal checks for the reason above. Fills a blank from the levels
|
|
313
|
-
// the CHILD's model reports; never overrules a caller, and yields today's behaviour whenever there is no answer.
|
|
314
|
-
if (!executorRefusal && !modelRefusal) {
|
|
315
|
-
const advisedThinking = await adviseEffort({
|
|
316
|
-
session,
|
|
317
|
-
requested: request.thinking,
|
|
318
|
-
model: configured.model,
|
|
319
|
-
registry: ctx.modelRegistry,
|
|
252
|
+
let capacity: CapacityReservation | undefined;
|
|
253
|
+
let capacityRefusal: import("../src/kernel/refusals.ts").StructuredRefusal | undefined;
|
|
254
|
+
try {
|
|
255
|
+
if (!options.capacityReservation) await reconcileDelegationCapacity(session);
|
|
256
|
+
capacity = options.capacityReservation ?? reserveDelegationCapacity(session, ids.executionId, budget);
|
|
257
|
+
if (capacity.executionId !== ids.executionId || (budget !== undefined && capacity.childAllowance !== budget))
|
|
258
|
+
throw new GovernanceRefusal(
|
|
259
|
+
structuredRefusal("FANOUT_EXCEEDED", "capacity reservation does not match its execution"),
|
|
260
|
+
);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (!(error instanceof GovernanceRefusal)) throw error;
|
|
263
|
+
capacityRefusal = structuredRefusal(error.code, error.message, error.details);
|
|
264
|
+
}
|
|
265
|
+
let executorEntered = false;
|
|
266
|
+
try {
|
|
267
|
+
await session.ensureDefinitions?.();
|
|
268
|
+
assertDefinitionIdentity(session, spec);
|
|
269
|
+
const { toolCallId, onProgress, preApproved, taskFrom, taskFromExecutionId, approvalFacts } = options;
|
|
270
|
+
// pi resolves a BARE model id to an unauthenticated provider and the child dies at startup — the id
|
|
271
|
+
// alone is not enough, it must be qualified with its provider (`Model<Api>` carries both).
|
|
272
|
+
const defaultModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
273
|
+
let runtimeRefusal: import("../src/kernel/refusals.ts").StructuredRefusal | undefined;
|
|
274
|
+
let configured: import("./definition-runtime.ts").ResolvedDefinitionRuntime = {
|
|
275
|
+
model: spec.model ?? defaultModel,
|
|
276
|
+
modelSource: spec.model ? "explicit" : "pi",
|
|
277
|
+
thinkingSource: "pi",
|
|
278
|
+
};
|
|
279
|
+
try {
|
|
280
|
+
configured =
|
|
281
|
+
options.resolvedRuntime ??
|
|
282
|
+
resolveDefinitionRuntime({
|
|
283
|
+
definition: spec.agent,
|
|
284
|
+
explicit: { model: spec.model, thinking: spec.thinking },
|
|
285
|
+
session: session.definitionRuntimeOverrides,
|
|
286
|
+
settings: session.definitionRuntimeSettings,
|
|
287
|
+
piModel: defaultModel,
|
|
288
|
+
piThinking: session.currentThinking?.(),
|
|
289
|
+
authoredPreferences: spec.agent ? session.definitions.get(spec.agent)?.runtimePreferences : undefined,
|
|
290
|
+
unavailable: (choice) => runtimePairUnavailable(choice, ctx.modelRegistry, session.allowUnresolvedModels),
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
runtimeRefusal = structuredRefusal("MODEL_UNRESOLVED", `runtime selection refused: ${String(error)}`);
|
|
294
|
+
}
|
|
295
|
+
const resolvedRuntime = configured;
|
|
296
|
+
const request = {
|
|
320
297
|
task: spec.task,
|
|
321
|
-
executionId: ids.executionId,
|
|
322
298
|
agent: spec.agent,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
299
|
+
tools: spec.tools,
|
|
300
|
+
model: configured.model,
|
|
301
|
+
thinking: configured.thinking,
|
|
302
|
+
context: spec.context,
|
|
303
|
+
correlation: spec.workspace
|
|
304
|
+
? { ...(spec.correlation ?? {}), workspace_id: spec.workspace.workspace_id }
|
|
305
|
+
: spec.correlation,
|
|
306
|
+
// The binding's workspace comes from the ROUTING SPEC, which is resolved against the operator
|
|
307
|
+
// registry and leased before any human is asked — never from `correlation`, which is a model-supplied
|
|
308
|
+
// claim that nothing validates when no spec accompanies it (R-110).
|
|
309
|
+
boundWorkspaceId: spec.workspace?.workspace_id,
|
|
310
|
+
boundContextId: spec.correlation?.context_id,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// ADR-0031: herdr was DEMANDED (`PI_DADDY_HERDR=1`) and is not answering. Refused rather than relocated —
|
|
314
|
+
// the operator chose that over falling back, so the ledger can never name a child that ran somewhere nobody
|
|
315
|
+
// chose.
|
|
316
|
+
//
|
|
317
|
+
// **Decided BEFORE the gate, and the ordering is a fix.** This sat after `planWithApprovals`, which opens the
|
|
318
|
+
// approval dialog — so with herdr down a human was asked to approve `tool:bash`, answered *Always*, and was
|
|
319
|
+
// then refused anyway. Measured: the answer still reached `process.env.PI_DADDY_APPROVED`, still wrote a
|
|
320
|
+
// **30-day project-wide** entry to the persisted store, and still produced a ledger line asserting a human
|
|
321
|
+
// approved `bash` for a child that never existed. A refused operation must not leave authority behind, and
|
|
322
|
+
// asking for permission that cannot be used is R-25's fatigue shape with nothing bought.
|
|
323
|
+
//
|
|
324
|
+
// `ctx: null` rather than skipping the plan entirely: the ledger still gets a full, honest record of what was
|
|
325
|
+
// requested and refused, and stored approvals still count toward it — nothing is *hidden*, only nobody is
|
|
326
|
+
// *asked*. It is the same argument `/grants` uses for its preview.
|
|
327
|
+
let executorRefusal = session.executor.refusal;
|
|
328
|
+
const modelRefusal =
|
|
329
|
+
runtimeRefusal ??
|
|
330
|
+
preflightModel(request.model, ctx.modelRegistry, session.modelResolutionCache, session.allowUnresolvedModels);
|
|
331
|
+
const { extra, refusal: nativeRefusal } = await nativeDelegationContext(
|
|
332
|
+
session,
|
|
333
|
+
ids,
|
|
334
|
+
capacity?.childAllowance ?? 0,
|
|
335
|
+
Boolean(executorRefusal || modelRefusal || capacityRefusal),
|
|
336
|
+
);
|
|
337
|
+
executorRefusal ||= nativeRefusal;
|
|
338
|
+
|
|
339
|
+
const planContext = extra;
|
|
340
|
+
let preparedWorkspace: PreparedWorkspace | undefined;
|
|
341
|
+
let approvalOutcome: ApprovalOutcome | undefined;
|
|
342
|
+
let plan: ReturnType<typeof planDelegation>;
|
|
336
343
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
344
|
+
if (spec.workspace && !executorRefusal && !modelRefusal && !capacityRefusal) {
|
|
345
|
+
// Check non-liftable refusals before taking a lease, and take the lease before asking a human. This
|
|
346
|
+
// preserves both anti-race rules: a doomed spawn cannot bank approval, and a conflicting writer starts
|
|
347
|
+
// no child process.
|
|
348
|
+
const preview = await planWithApprovals(session, request, planContext, null, signal, preApproved);
|
|
349
|
+
plan = preview.plan;
|
|
350
|
+
if (plan.ok || shouldSeekApproval(plan.result)) {
|
|
351
|
+
try {
|
|
352
|
+
preparedWorkspace = await prepareDelegationWorkspace({
|
|
353
|
+
...(session.workspacePin ? { workspacePin: session.workspacePin } : {}),
|
|
354
|
+
spec: { ...spec.workspace, access: governedWorkspaceAccess(spec.workspace.access, plan.requested) },
|
|
355
|
+
correlation: spec.correlation,
|
|
356
|
+
childId: ids.childId,
|
|
357
|
+
episodeId: session.episodeId,
|
|
358
|
+
executionId: ids.executionId,
|
|
359
|
+
parentExecutionId: ids.parentExecutionId,
|
|
360
|
+
signal,
|
|
361
|
+
ledgerPath: session.ledgerPath,
|
|
362
|
+
});
|
|
363
|
+
request.correlation = preparedWorkspace.correlation;
|
|
364
|
+
const gated = await planWithApprovals(session, request, planContext, ctx, signal, preApproved);
|
|
365
|
+
plan = gated.plan;
|
|
366
|
+
approvalOutcome = gated.approval;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
const value =
|
|
369
|
+
error instanceof GovernanceRefusal
|
|
370
|
+
? { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) }
|
|
371
|
+
: structuredRefusal("WORKSPACE_LEASE_STALE", `workspace setup failed (${String(error)})`);
|
|
372
|
+
plan = { ...plan, ok: false, reason: value.message, refusal: value };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
const gated = await planWithApprovals(
|
|
377
|
+
session,
|
|
378
|
+
request,
|
|
379
|
+
planContext,
|
|
380
|
+
executorRefusal || modelRefusal || capacityRefusal ? null : ctx,
|
|
381
|
+
signal,
|
|
382
|
+
preApproved,
|
|
383
|
+
);
|
|
384
|
+
plan = gated.plan;
|
|
385
|
+
approvalOutcome = gated.approval;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (capacityRefusal) {
|
|
389
|
+
plan = { ...plan, ok: false, reason: capacityRefusal.message, refusal: capacityRefusal };
|
|
390
|
+
} else if (executorRefusal) {
|
|
391
|
+
const message = `grants: ${executorRefusal}`;
|
|
392
|
+
plan = { ...plan, ok: false, reason: message, refusal: structuredRefusal("EXECUTOR_UNAVAILABLE", message) };
|
|
393
|
+
} else if (modelRefusal) {
|
|
394
|
+
// This is a routing preflight, not a grant decision: preserve the resolved capability facts and replace
|
|
395
|
+
// only the outcome. It is still recorded by the common refusal path, and no lease, dialog or child starts.
|
|
396
|
+
plan = { ...plan, ok: false, reason: modelRefusal.message, refusal: modelRefusal };
|
|
397
|
+
}
|
|
356
398
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
399
|
+
// The capability decision provisions the child, so this append remains load-bearing and fails closed.
|
|
400
|
+
try {
|
|
401
|
+
await recordDelegationDecision({
|
|
402
|
+
session,
|
|
403
|
+
plan,
|
|
404
|
+
ids,
|
|
405
|
+
agent: spec.agent,
|
|
406
|
+
taskFrom,
|
|
407
|
+
taskFromExecutionId,
|
|
408
|
+
approval: approvalOutcome,
|
|
409
|
+
approvalFacts,
|
|
410
|
+
});
|
|
411
|
+
} catch (error) {
|
|
412
|
+
plan = {
|
|
413
|
+
...plan,
|
|
414
|
+
ok: false,
|
|
415
|
+
reason: `grants: ledger write failed, denying — ${String(error)}`,
|
|
416
|
+
refusal: structuredRefusal("LEDGER_WRITE_FAILED", `grants: ledger write failed, denying — ${String(error)}`),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (!plan.ok) {
|
|
421
|
+
// EVERY refusal reached after the gate ran. Gating on `ledgerDenied` left three post-gate refusals
|
|
422
|
+
// stranding a 30-day approval — most reachably a human declining the SECOND of two gated capabilities,
|
|
423
|
+
// which needs no fault at all. The predicate is now the rule itself, so it cannot drift from it again.
|
|
424
|
+
if (ctx) await unbankApprovals(session, ctx, approvalOutcome?.banked);
|
|
425
|
+
// Guarded: this contains a `strict: true` append, and on the path where the ledger is already known
|
|
426
|
+
// unwritable an unguarded call replaced the governance refusal, and its code, with a ledger error.
|
|
364
427
|
try {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
spec: { ...spec.workspace, access: governedWorkspaceAccess(spec.workspace.access, plan.requested) },
|
|
368
|
-
correlation: spec.correlation,
|
|
428
|
+
await releaseDelegationWorkspace({
|
|
429
|
+
prepared: preparedWorkspace,
|
|
369
430
|
childId: ids.childId,
|
|
370
431
|
episodeId: session.episodeId,
|
|
371
432
|
executionId: ids.executionId,
|
|
372
433
|
parentExecutionId: ids.parentExecutionId,
|
|
373
|
-
signal,
|
|
374
434
|
ledgerPath: session.ledgerPath,
|
|
435
|
+
reason: "refused",
|
|
375
436
|
});
|
|
376
|
-
request.correlation = preparedWorkspace.correlation;
|
|
377
|
-
const gated = await planWithApprovals(session, request, planContext, ctx, signal, preApproved);
|
|
378
|
-
plan = gated.plan;
|
|
379
|
-
approvalOutcome = gated.approval;
|
|
380
437
|
} catch (error) {
|
|
381
|
-
|
|
382
|
-
error instanceof GovernanceRefusal
|
|
383
|
-
? { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) }
|
|
384
|
-
: structuredRefusal("WORKSPACE_LEASE_STALE", `workspace setup failed (${String(error)})`);
|
|
385
|
-
plan = { ...plan, ok: false, reason: value.message, refusal: value };
|
|
438
|
+
plan = { ...plan, reason: `${plan.reason ?? "refused"}; workspace release record failed: ${String(error)}` };
|
|
386
439
|
}
|
|
440
|
+
return {
|
|
441
|
+
ok: false,
|
|
442
|
+
text: "",
|
|
443
|
+
reason: plan.reason,
|
|
444
|
+
granted: [],
|
|
445
|
+
depth: plan.childDepth,
|
|
446
|
+
exitCode: null,
|
|
447
|
+
...(plan.refusal ? { refusal: plan.refusal } : {}),
|
|
448
|
+
};
|
|
387
449
|
}
|
|
388
|
-
} else {
|
|
389
|
-
const gated = await planWithApprovals(
|
|
390
|
-
session,
|
|
391
|
-
request,
|
|
392
|
-
planContext,
|
|
393
|
-
executorRefusal || modelRefusal ? null : ctx,
|
|
394
|
-
signal,
|
|
395
|
-
preApproved,
|
|
396
|
-
);
|
|
397
|
-
plan = gated.plan;
|
|
398
|
-
approvalOutcome = gated.approval;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (executorRefusal) {
|
|
402
|
-
const message = `grants: ${executorRefusal}`;
|
|
403
|
-
plan = { ...plan, ok: false, reason: message, refusal: structuredRefusal("EXECUTOR_UNAVAILABLE", message) };
|
|
404
|
-
} else if (modelRefusal) {
|
|
405
|
-
// This is a routing preflight, not a grant decision: preserve the resolved capability facts and replace
|
|
406
|
-
// only the outcome. It is still recorded by the common refusal path, and no lease, dialog or child starts.
|
|
407
|
-
plan = { ...plan, ok: false, reason: modelRefusal.message, refusal: modelRefusal };
|
|
408
|
-
}
|
|
409
450
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
451
|
+
executorEntered = true;
|
|
452
|
+
return await executePlannedChild({
|
|
453
|
+
capacityReservation: capacity,
|
|
413
454
|
session,
|
|
414
455
|
plan,
|
|
415
|
-
ids,
|
|
416
456
|
agent: spec.agent,
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
457
|
+
childId: ids.childId,
|
|
458
|
+
executionId: ids.executionId,
|
|
459
|
+
parentExecutionId: ids.parentExecutionId,
|
|
460
|
+
toolCallId: options.toolCallId,
|
|
461
|
+
cwd: ctx.cwd,
|
|
462
|
+
preparedWorkspace,
|
|
463
|
+
resolvedRuntime,
|
|
464
|
+
signal,
|
|
465
|
+
onProgress,
|
|
421
466
|
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (!plan.ok) {
|
|
432
|
-
// EVERY refusal reached after the gate ran. Gating on `ledgerDenied` left three post-gate refusals
|
|
433
|
-
// stranding a 30-day approval — most reachably a human declining the SECOND of two gated capabilities,
|
|
434
|
-
// which needs no fault at all. The predicate is now the rule itself, so it cannot drift from it again.
|
|
435
|
-
if (ctx) await unbankApprovals(session, ctx, approvalOutcome?.banked);
|
|
436
|
-
// Guarded: this contains a `strict: true` append, and on the path where the ledger is already known
|
|
437
|
-
// unwritable an unguarded call replaced the governance refusal, and its code, with a ledger error.
|
|
438
|
-
try {
|
|
439
|
-
await releaseDelegationWorkspace({
|
|
440
|
-
prepared: preparedWorkspace,
|
|
441
|
-
childId: ids.childId,
|
|
442
|
-
episodeId: session.episodeId,
|
|
443
|
-
executionId: ids.executionId,
|
|
444
|
-
parentExecutionId: ids.parentExecutionId,
|
|
445
|
-
ledgerPath: session.ledgerPath,
|
|
446
|
-
reason: "refused",
|
|
447
|
-
});
|
|
448
|
-
} catch (error) {
|
|
449
|
-
plan = { ...plan, reason: `${plan.reason ?? "refused"}; workspace release record failed: ${String(error)}` };
|
|
450
|
-
}
|
|
451
|
-
return {
|
|
452
|
-
ok: false,
|
|
453
|
-
text: "",
|
|
454
|
-
reason: plan.reason,
|
|
455
|
-
granted: [],
|
|
456
|
-
depth: plan.childDepth,
|
|
457
|
-
exitCode: null,
|
|
458
|
-
...(plan.refusal ? { refusal: plan.refusal } : {}),
|
|
459
|
-
};
|
|
467
|
+
} finally {
|
|
468
|
+
capacity?.finalize(
|
|
469
|
+
executorEntered
|
|
470
|
+
? { state: "unknown", reason: "execution returned without qualified cleanup" }
|
|
471
|
+
: { state: "not-started", reason: "delegation ended before entering the executor" },
|
|
472
|
+
);
|
|
460
473
|
}
|
|
461
|
-
|
|
462
|
-
if (spec.episodeCostCeiling !== undefined) session.episodeCostGate.setCeiling(spec.episodeCostCeiling);
|
|
463
|
-
plan = {
|
|
464
|
-
...plan,
|
|
465
|
-
env: { ...plan.env, [ENV_EPISODE_COST_CEILING]: String(session.episodeCostGate.ceiling) },
|
|
466
|
-
};
|
|
467
|
-
const costUI = ctx.ui as typeof ctx.ui & {
|
|
468
|
-
input(title: string, placeholder?: string, opts?: { signal?: AbortSignal }): Promise<string | undefined>;
|
|
469
|
-
};
|
|
470
|
-
return executePlannedChild({
|
|
471
|
-
session,
|
|
472
|
-
plan,
|
|
473
|
-
agent: spec.agent,
|
|
474
|
-
childId: ids.childId,
|
|
475
|
-
executionId: ids.executionId,
|
|
476
|
-
parentExecutionId: ids.parentExecutionId,
|
|
477
|
-
toolCallId: options.toolCallId,
|
|
478
|
-
cwd: ctx.cwd,
|
|
479
|
-
preparedWorkspace,
|
|
480
|
-
resolvedRuntime,
|
|
481
|
-
signal,
|
|
482
|
-
onProgress,
|
|
483
|
-
costGateUI: {
|
|
484
|
-
hasUI: ctx.hasUI,
|
|
485
|
-
input: (title, placeholder, gateSignal) => costUI.input(title, placeholder, { signal: gateSignal }),
|
|
486
|
-
notify: (message) => ctx.ui.notify(message, "warning"),
|
|
487
|
-
},
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* The planner context for one delegation, including a `pruned` handoff narrowed by an advisor (ADR-0077).
|
|
493
|
-
*
|
|
494
|
-
* **Exported and taking its own `preview`, so the ordering is forced by a test rather than by a reviewer.** Three
|
|
495
|
-
* properties live here and each was, at some point in this change's history, true only because somebody had
|
|
496
|
-
* checked it by hand: an advisor is not asked for a delegation that is already refused; it is not asked until a
|
|
497
|
-
* plan says the `pruned` handoff actually survived the ceiling, the grant and the gate; and the ids it returns
|
|
498
|
-
* reach the planner. Reviewers measured all three by mutating the source and finding the suite still green. A
|
|
499
|
-
* function with a seam is the only version of this that a test can hold.
|
|
500
|
-
*/
|
|
501
|
-
export async function handoffPlanContext(input: {
|
|
502
|
-
session: Parameters<typeof advisePruning>[0]["session"];
|
|
503
|
-
base: Record<string, unknown>;
|
|
504
|
-
task: string;
|
|
505
|
-
executionId?: string;
|
|
506
|
-
toolCallId?: string;
|
|
507
|
-
/** A refusal is already certain, so nothing may be asked. */
|
|
508
|
-
blocked: boolean;
|
|
509
|
-
/** Plans with no human in the loop; its result decides whether an advisor is consulted at all. */
|
|
510
|
-
preview: () => Promise<{ handoff?: { mode: string } }>;
|
|
511
|
-
attribution?: Parameters<typeof advisePruning>[0]["attribution"];
|
|
512
|
-
signal?: AbortSignal;
|
|
513
|
-
}): Promise<Record<string, unknown>> {
|
|
514
|
-
if (input.blocked) return { ...input.base };
|
|
515
|
-
const plan = await input.preview();
|
|
516
|
-
if (plan.handoff?.mode !== "pruned") return { ...input.base };
|
|
517
|
-
const ids = await advisePruning({
|
|
518
|
-
session: input.session,
|
|
519
|
-
granted: plan.handoff as Parameters<typeof advisePruning>[0]["granted"],
|
|
520
|
-
task: input.task,
|
|
521
|
-
...(input.executionId ? { executionId: input.executionId } : {}),
|
|
522
|
-
...(input.toolCallId ? { toolCallId: input.toolCallId } : {}),
|
|
523
|
-
...(input.attribution ? { attribution: input.attribution } : {}),
|
|
524
|
-
...(input.signal ? { signal: input.signal } : {}),
|
|
525
|
-
});
|
|
526
|
-
return ids ? { ...input.base, handoffTurnIds: ids } : { ...input.base };
|
|
527
474
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readCapturedWorkerReceipt } from "../src/governance/captured-worker-record.ts";
|
|
2
|
+
import { createCapacityAllocator, type CapacityAllocator, type CapacityReservation } from "../src/kernel/capacity.ts";
|
|
3
|
+
import { budgetFromEnv } from "../src/kernel/fanout.ts";
|
|
4
|
+
import { GovernanceRefusal, refusal, type StructuredRefusal } from "../src/kernel/refusals.ts";
|
|
5
|
+
import type { ReloadLifecycle } from "./reload-environment.ts";
|
|
6
|
+
import type { GrantsSession } from "./session.ts";
|
|
7
|
+
|
|
8
|
+
/** Live reservations belong to the real owner, not to replaceable extension/configuration objects. */
|
|
9
|
+
export interface OwnerCapacity {
|
|
10
|
+
allocator: CapacityAllocator;
|
|
11
|
+
refusal?: StructuredRefusal;
|
|
12
|
+
}
|
|
13
|
+
export function capacityForLifecycle(lifecycle: ReloadLifecycle, raw: string | undefined): OwnerCapacity {
|
|
14
|
+
let configured: number;
|
|
15
|
+
try {
|
|
16
|
+
configured = budgetFromEnv(raw);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
const rejected = refusal("FANOUT_EXCEEDED", `PI_DADDY_FANOUT refuses delegation: ${String(error)}`);
|
|
19
|
+
lifecycle.capacity ??= { allocator: createCapacityAllocator(0), refusal: rejected };
|
|
20
|
+
return { allocator: lifecycle.capacity.allocator, refusal: rejected };
|
|
21
|
+
}
|
|
22
|
+
lifecycle.capacity ??= { allocator: createCapacityAllocator(configured) };
|
|
23
|
+
if (lifecycle.capacity.refusal) return lifecycle.capacity;
|
|
24
|
+
if (configured !== lifecycle.capacity.allocator.total)
|
|
25
|
+
return {
|
|
26
|
+
allocator: lifecycle.capacity.allocator,
|
|
27
|
+
refusal: refusal(
|
|
28
|
+
"FANOUT_EXCEEDED",
|
|
29
|
+
"PI_DADDY_FANOUT changed for an existing owner; start a new session to change capacity without replacing live reservations",
|
|
30
|
+
),
|
|
31
|
+
};
|
|
32
|
+
return lifecycle.capacity;
|
|
33
|
+
}
|
|
34
|
+
export function reserveDelegationCapacity(
|
|
35
|
+
session: GrantsSession,
|
|
36
|
+
executionId: string,
|
|
37
|
+
childAllowance?: number,
|
|
38
|
+
): CapacityReservation {
|
|
39
|
+
if (session.capacityRefusal) throw new GovernanceRefusal(session.capacityRefusal);
|
|
40
|
+
const result = session.capacity.reserve(executionId, childAllowance ?? Math.max(0, session.capacity.available - 1));
|
|
41
|
+
if (!result.ok) throw new GovernanceRefusal(refusal("FANOUT_EXCEEDED", result.reason));
|
|
42
|
+
return result.reservation;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Recheck only original bound receipt paths; later proof frees capacity, never rewrites a failed outcome. */
|
|
46
|
+
export async function reconcileDelegationCapacity(session: Pick<GrantsSession, "capacity">): Promise<void> {
|
|
47
|
+
for (const reservation of session.capacity.retainedReservations) {
|
|
48
|
+
const identity = reservation.identity;
|
|
49
|
+
if (!identity) continue;
|
|
50
|
+
const receipt = await readCapturedWorkerReceipt(identity);
|
|
51
|
+
if (receipt) reservation.finalize({ state: "settled", identity, receipt });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseInherited } from "../src/kernel/approval.ts";
|
|
2
2
|
import { DELEGATE_CAPABILITY } from "../src/kernel/delegate.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { capacityForLifecycle } from "./session-capacity.ts";
|
|
4
4
|
import { isEpisodeId } from "../src/kernel/episode-id.ts";
|
|
5
5
|
import { WILDCARD } from "../src/kernel/pi-tools.ts";
|
|
6
6
|
import {
|
|
@@ -54,7 +54,10 @@ export function reconcileSessionEnvironment(
|
|
|
54
54
|
? lifecycle.episodeId
|
|
55
55
|
: session.episodeId;
|
|
56
56
|
lifecycle.episodeId = session.episodeId;
|
|
57
|
-
|
|
57
|
+
const capacityState = capacityForLifecycle(lifecycle, environment[ENV_FANOUT]);
|
|
58
|
+
session.capacity = capacityState.allocator;
|
|
59
|
+
session.capacityRefusal = capacityState.refusal;
|
|
60
|
+
session.fanoutBudget = session.capacity.total;
|
|
58
61
|
session.mayDelegate =
|
|
59
62
|
!session.governed || session.inherited.includes(DELEGATE_CAPABILITY) || session.inherited.includes(WILDCARD);
|
|
60
63
|
session.allowUnresolvedModels = environment[ENV_ALLOW_UNRESOLVED_MODELS] === "1";
|