pi-daddy 0.18.0 → 0.19.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.
Files changed (130) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +46 -2
  3. package/contracts/ledger/v2/README.md +59 -0
  4. package/contracts/ledger/v2/fixtures/capability-decision.json +95 -0
  5. package/contracts/ledger/v2/fixtures/check-receipt.json +40 -0
  6. package/contracts/ledger/v2/fixtures/child-lifecycle.json +42 -0
  7. package/contracts/ledger/v2/fixtures/workspace-lease.json +41 -0
  8. package/contracts/ledger/v2/ledger-event.schema.json +633 -0
  9. package/dist/approval-prompt.d.ts +2 -1
  10. package/dist/approval-prompt.d.ts.map +1 -1
  11. package/dist/approval-prompt.js +9 -0
  12. package/dist/approval-prompt.js.map +1 -1
  13. package/dist/approval.d.ts +4 -2
  14. package/dist/approval.d.ts.map +1 -1
  15. package/dist/approval.js +4 -0
  16. package/dist/approval.js.map +1 -1
  17. package/dist/capabilities.d.ts +105 -0
  18. package/dist/capabilities.d.ts.map +1 -1
  19. package/dist/capabilities.js +161 -3
  20. package/dist/capabilities.js.map +1 -1
  21. package/dist/catalog.d.ts +15 -1
  22. package/dist/catalog.d.ts.map +1 -1
  23. package/dist/catalog.js +54 -3
  24. package/dist/catalog.js.map +1 -1
  25. package/dist/check-runner.d.ts.map +1 -1
  26. package/dist/check-runner.js +5 -7
  27. package/dist/check-runner.js.map +1 -1
  28. package/dist/cli.d.ts.map +1 -1
  29. package/dist/cli.js +21 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/definitions.d.ts.map +1 -1
  32. package/dist/definitions.js +7 -1
  33. package/dist/definitions.js.map +1 -1
  34. package/dist/delegate.d.ts.map +1 -1
  35. package/dist/delegate.js +32 -4
  36. package/dist/delegate.js.map +1 -1
  37. package/dist/delegation-approval.d.ts.map +1 -1
  38. package/dist/delegation-approval.js +37 -12
  39. package/dist/delegation-approval.js.map +1 -1
  40. package/dist/executor.d.ts +2 -1
  41. package/dist/executor.d.ts.map +1 -1
  42. package/dist/executor.js +1 -0
  43. package/dist/executor.js.map +1 -1
  44. package/dist/grant-env.d.ts +2 -0
  45. package/dist/grant-env.d.ts.map +1 -1
  46. package/dist/grant-env.js +26 -3
  47. package/dist/grant-env.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/init.d.ts +13 -1
  53. package/dist/init.d.ts.map +1 -1
  54. package/dist/init.js +35 -2
  55. package/dist/init.js.map +1 -1
  56. package/dist/lease-helper.d.ts +58 -0
  57. package/dist/lease-helper.d.ts.map +1 -0
  58. package/dist/lease-helper.js +94 -0
  59. package/dist/lease-helper.js.map +1 -0
  60. package/dist/lease-record.d.ts +15 -3
  61. package/dist/lease-record.d.ts.map +1 -1
  62. package/dist/lease-record.js.map +1 -1
  63. package/dist/ledger-events.d.ts +35 -14
  64. package/dist/ledger-events.d.ts.map +1 -1
  65. package/dist/ledger-events.js +41 -0
  66. package/dist/ledger-events.js.map +1 -1
  67. package/dist/ledger.d.ts +10 -5
  68. package/dist/ledger.d.ts.map +1 -1
  69. package/dist/ledger.js +13 -3
  70. package/dist/ledger.js.map +1 -1
  71. package/dist/propagation.d.ts +16 -0
  72. package/dist/propagation.d.ts.map +1 -1
  73. package/dist/propagation.js +22 -2
  74. package/dist/propagation.js.map +1 -1
  75. package/dist/refusals.d.ts +1 -1
  76. package/dist/refusals.d.ts.map +1 -1
  77. package/dist/refusals.js +2 -0
  78. package/dist/refusals.js.map +1 -1
  79. package/dist/resolve.d.ts +10 -0
  80. package/dist/resolve.d.ts.map +1 -1
  81. package/dist/resolve.js +33 -3
  82. package/dist/resolve.js.map +1 -1
  83. package/dist/routing-authority.d.ts +71 -0
  84. package/dist/routing-authority.d.ts.map +1 -0
  85. package/dist/routing-authority.js +100 -0
  86. package/dist/routing-authority.js.map +1 -0
  87. package/dist/skill-packages.d.ts +11 -5
  88. package/dist/skill-packages.d.ts.map +1 -1
  89. package/dist/skill-packages.js +20 -11
  90. package/dist/skill-packages.js.map +1 -1
  91. package/dist/workspace-lease.d.ts +15 -3
  92. package/dist/workspace-lease.d.ts.map +1 -1
  93. package/dist/workspace-lease.js +81 -24
  94. package/dist/workspace-lease.js.map +1 -1
  95. package/dist/workspace.d.ts +25 -0
  96. package/dist/workspace.d.ts.map +1 -1
  97. package/dist/workspace.js +142 -5
  98. package/dist/workspace.js.map +1 -1
  99. package/extensions/delegation.ts +7 -1
  100. package/extensions/grants-command.ts +11 -1
  101. package/extensions/grants.ts +6 -1
  102. package/extensions/init-command.ts +33 -2
  103. package/extensions/session-report.ts +13 -20
  104. package/extensions/session.ts +9 -1
  105. package/extensions/workspace-runtime.ts +34 -4
  106. package/package.json +8 -3
  107. package/src/approval-prompt.ts +2 -1
  108. package/src/approval.ts +4 -2
  109. package/src/capabilities.ts +173 -4
  110. package/src/catalog.ts +62 -4
  111. package/src/check-runner.ts +8 -8
  112. package/src/cli.ts +24 -1
  113. package/src/definitions.ts +7 -1
  114. package/src/delegate.ts +42 -4
  115. package/src/delegation-approval.ts +37 -12
  116. package/src/executor.ts +2 -1
  117. package/src/grant-env.ts +39 -7
  118. package/src/index.ts +1 -0
  119. package/src/init.ts +40 -2
  120. package/src/lease-helper.ts +97 -0
  121. package/src/lease-record.ts +15 -3
  122. package/src/ledger-events.ts +66 -22
  123. package/src/ledger.ts +30 -6
  124. package/src/propagation.ts +23 -2
  125. package/src/refusals.ts +2 -0
  126. package/src/resolve.ts +35 -3
  127. package/src/routing-authority.ts +121 -0
  128. package/src/skill-packages.ts +20 -13
  129. package/src/workspace-lease.ts +84 -24
  130. package/src/workspace.ts +179 -6
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
@@ -183,7 +183,13 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
183
183
  check_receipt_id: Type.Optional(Type.String()),
184
184
  });
185
185
  const workspaceShape = Type.Object({
186
- workspace_id: Type.String({ description: "ID from the operator-owned workspace registry." }),
186
+ // `minLength: 1` because an empty id is not a "no workspace" signal — it is a malformed one, and the
187
+ // planner now says so. Refused at the schema as well as in the planner: a model-facing parameter that
188
+ // reaches a capability id should be bounded at both ends.
189
+ workspace_id: Type.String({
190
+ minLength: 1,
191
+ description: "ID from the operator-owned workspace registry. Routing here needs workspace:<id> in the grant.",
192
+ }),
187
193
  access: Type.Union([Type.Literal("read"), Type.Literal("write")]),
188
194
  });
189
195
 
@@ -330,9 +330,19 @@ handler: async (args: string, ctx: any) => {
330
330
  ` approvals ${sessionApprovals.size} this session, ${valid.size} persisted` +
331
331
  `${inheritedApprovals.size > 0 ? `, ${inheritedApprovals.size} inherited` : ""}` +
332
332
  ` — /grants approvals`,
333
+ // Every kind the catalog can hold is named, and the numbers add up to the total on purpose. `workspace`
334
+ // was missing when ADR-0035's review added the kind: the total counted them and the breakdown did not,
335
+ // so `catalog 16 capabilities — 9 builtin, 0 extension, 2 skill, 2 agent-type` printed a sum of 13. And
336
+ // the comment that justified putting workspaces in the catalog said it was "for `/grants` to list what
337
+ // this session may route to", which nothing then did — the claim was in the commit and the behaviour
338
+ // was not. A kind added without a term here silently stops adding up.
333
339
  ` catalog ${catalog.all.length} capabilities — ` +
334
340
  `${catalog.byKind("builtin").length} builtin, ${catalog.byKind("extension").length} extension, ` +
335
- `${catalog.byKind("skill").length} skill, ${catalog.byKind("agentType").length} agent-type`,
341
+ `${catalog.byKind("skill").length} skill, ${catalog.byKind("agentType").length} agent-type, ` +
342
+ `${catalog.byKind("workspace").length} workspace`,
343
+ ...(catalog.byKind("workspace").length > 0
344
+ ? [` routable ${catalog.byKind("workspace").join(", ")} — held ones only are usable (ADR-0035)`]
345
+ : []),
336
346
  ];
337
347
  // Runs the REAL planner AND the real approval step over each definition, so this listing cannot
338
348
  // disagree with what a spawn would do — the R-28 lesson, kept structural rather than remembered.
@@ -25,6 +25,7 @@ import { fileURLToPath } from "node:url";
25
25
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
26
26
  import { WILDCARD } from "../src/pi-tools.ts";
27
27
  import { buildCatalog } from "../src/catalog.ts";
28
+ import { ENV_WORKSPACE_REGISTRY } from "../src/workspace.ts";
28
29
  import { appendRecord, buildRecord } from "../src/ledger.ts";
29
30
  import { openPaneCount, reapOpenPanesAsync } from "../src/pane-reaper.ts";
30
31
  import {
@@ -216,7 +217,11 @@ export default function (pi: ExtensionAPI) {
216
217
  // Keep the handle: a concurrent `delegate` awaits this rather than reading a half-built catalog.
217
218
  // The `catch` resolves to the CURRENT catalog rather than rejecting, so a failed refresh degrades
218
219
  // to the previous view instead of failing every delegation in the session.
219
- session.catalogReady = buildCatalog({ cwd: session.cwd, observedTools: names })
220
+ session.catalogReady = buildCatalog({
221
+ cwd: session.cwd,
222
+ observedTools: names,
223
+ registryPath: process.env[ENV_WORKSPACE_REGISTRY],
224
+ })
220
225
  .then((c) => (session.catalog = c))
221
226
  .catch(() => session.catalog);
222
227
  } catch {
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { discoverSkillPackages } from "../src/skill-packages.ts";
11
11
  import { applyInit, planInit } from "../src/init.ts";
12
+ import { registeredWorkspaceIds } from "../src/workspace.ts";
12
13
  import { saveGrant, grantStorePath } from "../src/grant-store.ts";
13
14
  import { expandSubsumed, SUBSUMPTION, type Capability } from "../src/resolve.ts";
14
15
  import type { GrantsSession } from "./session.ts";
@@ -50,7 +51,7 @@ export async function runInit(
50
51
  return;
51
52
  }
52
53
 
53
- const plan = planInit(packages, ctx.cwd);
54
+ const plan = planInit(packages, ctx.cwd, await registeredWorkspaceIds());
54
55
  const outcome = await applyInit(plan);
55
56
  const lines = [
56
57
  `grants: ${plan.skills.length} definition(s) from ${packages.map((p) => `${p.name}@${p.version}`).join(", ")}`,
@@ -81,9 +82,28 @@ export async function runInit(
81
82
  for (const name of neededBy) grant.add(`agent:${name}` as Capability);
82
83
  continue;
83
84
  }
85
+ // The consequence sentence is per capability, and it reads the gate **in effect for this session** —
86
+ // `session.gated`, which is `PI_GRANTS_GATED` when the operator set it and `DEFAULT_GATED` otherwise.
87
+ //
88
+ // The first version read `DEFAULT_GATED` directly, which is the compile-time constant `["tool:bash"]`, and
89
+ // `tool:bash` is consumed by the branch above — so the branch was **unreachable in every configuration**,
90
+ // and with `PI_GRANTS_GATED="tool:bash,tool:write"` (the value `renderGrantEnv` itself suggests) the
91
+ // dialog still printed the exact false sentence the fix claimed to remove. Dead code beside a claim that
92
+ // it worked, which is this project's failure mode, in the commit correcting that failure mode.
93
+ // No subsumption closure here, and the absence is deliberate. A `|| SUBSUMPTION[capability]…` disjunct was
94
+ // added and is **dead in every configuration**: `SUBSUMPTION` has one key, `tool:bash`, and `tool:bash` is
95
+ // consumed by the ternary below before `gatedAtSpawn` is ever read. Two reviewers and a mutation confirmed
96
+ // it changed no result — dead code beside a claim that it worked, in the hunk whose comment denounces
97
+ // exactly that. A second `SUBSUMPTION` entry is the moment to add it back.
98
+ const gatedAtSpawn = session.gated.includes(capability);
84
99
  const answer = await ctx.ui.select(
85
100
  `grants: grant ${capability} to sub-agents?\n needed by: ${neededBy.join(", ")}\n` +
86
- ` this can change your machine — ${capability === "tool:bash" ? "and bash also confers write and edit" : "it is not gated, so no dialog at spawn time"}`,
101
+ ` this can change your machine — ` +
102
+ (capability === "tool:bash"
103
+ ? "and bash also confers write and edit"
104
+ : gatedAtSpawn
105
+ ? "a human is still asked at spawn time"
106
+ : "it is not gated, so no dialog at spawn time"),
87
107
  ["No", "Yes"],
88
108
  );
89
109
  if (answer === "Yes") {
@@ -121,6 +141,17 @@ export async function runInit(
121
141
  lines.push(` ALREADY CONFERRED, not asked about: ${alreadyConferred.join(", ")}`);
122
142
  }
123
143
  if (declined.length > 0) lines.push(` withheld: ${declined.join("; ")}`);
144
+ // Routing is LISTED, never granted (ADR-0028) — so the operator has to be told it exists, or the listing is
145
+ // invisible to anyone running `/grants init` in-session rather than reading the generated file. 0.19.0 made
146
+ // `workspace:<id>` mandatory for routing, so this is also the migration hint, and a breaking change whose
147
+ // migration is only discoverable by opening a file is not much of a migration.
148
+ if (plan.routableWorkspaces.length > 0) {
149
+ lines.push(
150
+ ` ROUTABLE WORKSPACES, listed and NOT granted: ${plan.routableWorkspaces.join(", ")}`,
151
+ ` routing a child to one needs its id in the grant (ADR-0035); add the ones this project may use to ` +
152
+ `${plan.grantEnvPath}. Which worktree a child starts in is not something a package can declare for you.`,
153
+ );
154
+ }
124
155
  lines.push(` live now (${finalGrant.length} capabilities) — no restart. /grants shows the verdicts.`);
125
156
  ctx.ui.notify(lines.join("\n"), "info");
126
157
  }
@@ -80,16 +80,6 @@ export async function reportSessionStart(session: GrantsSession, ctx: SessionRep
80
80
  } catch {
81
81
  /* never throw into the agent loop */
82
82
  }
83
- // R-47. `gatedBlocked` filters `requested`, and for a definition spawn `requested` is that
84
- // definition's CEILING — which never contains `agent:<name>`, because the authorisation check
85
- // (ADR-0017) is a separate, ungated branch. So `PI_GRANTS_GATED=agent:deploy`, written by an operator
86
- // who read "it attenuates like any other capability" and meant "ask me before deploy runs", produces
87
- // no dialog and no warning. It DOES bite when a definition passes the id down in its own
88
- // `allowed-tools`, so the flag half-works — which is worse than not working, and is R-25's shape in
89
- // the namespace ADR-0017 just promoted out of exactly that state.
90
- //
91
- // Warned rather than enforced: making it gate the spawn is a behaviour change and wants a decision.
92
- // Silence is the part that is indefensible either way.
93
83
  // `agent:*` grants no tools, but it authorises every definition in BOTH skill roots — including
94
84
  // `~/.pi/agent/skills/`, which other software installs into, so ADR-0017's "an operator-authored
95
85
  // file" is not true of everything it covers. Paired with a shell that is every body on disk running
@@ -103,16 +93,19 @@ export async function reportSessionStart(session: GrantsSession, ctx: SessionRep
103
93
  "warning",
104
94
  );
105
95
  }
106
- const inertGates = session.gated.filter((c) => c.startsWith("agent:"));
107
- if (inertGates.length > 0) {
108
- ctx.ui.notify(
109
- `grants: ${inertGates.join(", ")} in PI_GRANTS_GATED does NOT gate spawning that definition — ` +
110
- `the authorisation check for a definition is separate and ungated, so a human is never asked. ` +
111
- `It applies only where a definition passes the id down in its own allowed-tools. To control ` +
112
- `which definitions may run, withhold the agent: capability from PI_GRANTS_GRANT instead.`,
113
- "warning",
114
- );
115
- }
96
+ // REMOVED 2026-08-21. This warned that an `agent:` id in `PI_GRANTS_GATED` "does NOT gate spawning that
97
+ // definition — a human is never asked". It was R-47's PARTIAL fix (0.11.1) and became false one release
98
+ // later at 0.12.0, when ADR-0024's gate landed (`4673348`, "gating a definition asks before it runs") and
99
+ // a gated `agent:<name>` began blocking the spawn until somebody approved it. ADR-0024's own Costs section
100
+ // leaned on this warning — "mitigated by the fact that the warning shipped hours earlier tells them it
101
+ // currently does nothing" — and nothing retired it when its own decision made the mitigation false.
102
+ //
103
+ // So from 0.12.0 through 0.18.1 the session banner told operators to delete a gate that works, and
104
+ // `test-integration/governance.it.ts` REQUIRED it to, which is how a stale claim outlives the person who
105
+ // notices. It is R-28's shape in the direction that talks somebody OUT of a control. ADR-0035 then
106
+ // repeated the pattern one namespace over by claiming a `workspace:` gate that did not exist; both are
107
+ // gates now, so there is nothing inert left to warn about. Deleted rather than reworded — a warning with
108
+ // no live case is the next stale claim. R-134.
116
109
  // R-34. `verifyLedger` existed and nothing ran it, so a torn line was detectable and undetected —
117
110
  // and a check an operator has to know to run is not a control, it is a feature. Setting
118
111
  // `PI_GRANTS_LEDGER` already means "I want an audit trail"; noticing that the trail is damaged is
@@ -42,6 +42,7 @@ import {
42
42
  import type { Capability } from "../src/resolve.ts";
43
43
  import { loadDefinitions } from "../src/definitions.ts";
44
44
  import { buildCatalog } from "../src/catalog.ts";
45
+ import { ENV_WORKSPACE_REGISTRY } from "../src/workspace.ts";
45
46
  import { loadGrantSync, grantStorePath } from "../src/grant-store.ts";
46
47
  import { republishable } from "./approvals.ts";
47
48
 
@@ -210,7 +211,14 @@ export interface GrantsSession {
210
211
  */
211
212
  export async function loadProjectDefinitions(session: GrantsSession, cwd: string): Promise<void> {
212
213
  session.definitions = await loadDefinitions(cwd);
213
- session.catalogReady = buildCatalog({ cwd, observedTools: session.observedTools });
214
+ session.catalogReady = buildCatalog({
215
+ cwd,
216
+ observedTools: session.observedTools,
217
+ // ADR-0035: `workspace:<id>` is a capability, so the registered ids belong in the catalog the same way
218
+ // discovered definitions do — for `/grants` to list what this session may route to and for `init` to
219
+ // scaffold them. Read live rather than cached at load, because the registry is an operator file.
220
+ registryPath: process.env[ENV_WORKSPACE_REGISTRY],
221
+ });
214
222
  session.catalog = await session.catalogReady;
215
223
  }
216
224
 
@@ -23,12 +23,39 @@ export interface DelegationWorkspaceSpec {
23
23
  access: WorkspaceAccess;
24
24
  }
25
25
 
26
+ /**
27
+ * Tools that cannot change a worktree.
28
+ *
29
+ * **`tool:delegate` is deliberately NOT here, and that is a scope decision.** It was added, and it was true of
30
+ * the capability and false of the code: a routed child's cwd IS the leased root, `PI_GRANTS_LEDGER` was
31
+ * passed through relative (`init` scaffolds `.pi/grants.jsonl`), and a `read` lease takes no kernel lock — so
32
+ * two delegating children classified `read` both created `.pi/` in one worktree and neither excluded the
33
+ * other. Making `tool:delegate` non-writing requires first making every child-inherited path absolute, and
34
+ * that is a change to the ledger and lease plumbing rather than to ADR-0035. Tracked as R-141.
35
+ */
26
36
  const KNOWN_READ_ONLY_TOOLS = new Set(["tool:read", "tool:grep", "tool:find", "tool:ls"]);
27
37
 
28
- /** A model may ask for stricter coordination but cannot label a write-capable grant read-only. */
38
+ /**
39
+ * A model may ask for stricter coordination but cannot label a write-capable grant read-only.
40
+ *
41
+ * **Only a TOOL is considered, and that is a fix rather than an oversight.** The check used to require *every*
42
+ * requested capability to be a known read-only tool, which was correct while `tool:` and `ext:` were the only
43
+ * things that could appear — and became wrong the moment ADR-0035's review made `workspace:<id>` grantable to
44
+ * a child. Measured: `governedWorkspaceAccess("read", ["tool:read", "workspace:staging"])` returned `"write"`,
45
+ * so the intended shape — *route this child read-only and let it route its own grandchild* — silently took an
46
+ * exclusive writer lease, blocked every other writer on that root, and recorded `access: "write"` in the
47
+ * ledger when the operator had asked for `read`. A record asserting a stronger claim than anybody made is the
48
+ * same failure this whole review is about, pointing the other way.
49
+ *
50
+ * A `workspace:`, `agent:` or `skill:` id confers no filesystem ability whatsoever: routing chooses a
51
+ * directory, `agent:` authorises a definition whose ceiling is still clipped to the child's own grant, and
52
+ * `skill:` loads instructions. None of them can write, so none of them should force a writer lease. If a
53
+ * descendant does hold a write tool, that tool is in `requested` and this check refuses on its own terms.
54
+ */
29
55
  export function governedWorkspaceAccess(declared: WorkspaceAccess, requested: readonly Capability[]): WorkspaceAccess {
30
56
  if (declared === "write") return "write";
31
- return requested.every((capability) => KNOWN_READ_ONLY_TOOLS.has(capability)) ? "read" : "write";
57
+ const tools = requested.filter((c) => c.startsWith("tool:") || c.startsWith("ext:"));
58
+ return tools.every((capability) => KNOWN_READ_ONLY_TOOLS.has(capability)) ? "read" : "write";
32
59
  }
33
60
 
34
61
  export interface PreparedWorkspace {
@@ -133,8 +160,11 @@ export async function releaseDelegationWorkspace(input: {
133
160
  // A retained lease writes no `state: "released"`, so the record stays `active` and the NEXT owner reads
134
161
  // it as a crash — the exact blame `retained` was added to remove. Marking it keeps the successor honest;
135
162
  // R-104 was fixed in the release event's wording only.
136
- const outcome: LeaseReleaseOutcome | "retained" = input.retain
137
- ? (await input.prepared.lease.markRetained(input.reason), "retained")
163
+ // **Ledger what happened, not what was intended (R-152).** This used to discard `markRetained`'s result and
164
+ // write the word "retained" unconditionally, so a helper that had already died, or a lease already released,
165
+ // was still recorded as a pane that may still be live.
166
+ const outcome: LeaseReleaseOutcome = input.retain
167
+ ? await input.prepared.lease.markRetained(input.reason)
138
168
  : await input.prepared.lease.release(input.reason);
139
169
  if (input.ledgerPath) {
140
170
  await appendLedgerEvent(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-daddy",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Capability governance for pi sub-agents: spawn Agent Skills (SKILL.md) definitions whose allowed-tools becomes a grant that can only narrow going down a delegation tree, enforced by pi's own --tools allowlist, with an append-only ledger.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -128,7 +128,9 @@
128
128
  "./check-runner": {
129
129
  "types": "./dist/check-runner.d.ts",
130
130
  "default": "./dist/check-runner.js"
131
- }
131
+ },
132
+ "./contracts/ledger/v2/ledger-event.schema.json": "./contracts/ledger/v2/ledger-event.schema.json",
133
+ "./contracts/ledger/v2/fixtures/*.json": "./contracts/ledger/v2/fixtures/*.json"
132
134
  },
133
135
  "bin": {
134
136
  "pi-daddy": "./dist/cli.js"
@@ -137,6 +139,7 @@
137
139
  "dist",
138
140
  "extensions",
139
141
  "src",
142
+ "contracts",
140
143
  "README.md",
141
144
  "CHANGELOG.md",
142
145
  "LICENSE"
@@ -146,8 +149,10 @@
146
149
  "typecheck": "tsc -p tsconfig.check.json",
147
150
  "test": "node --test test/*.test.ts",
148
151
  "test:integration": "node --test test-integration/*.it.ts",
152
+ "contracts:generate": "node scripts/generate-ledger-v2-contract.ts",
149
153
  "prepack": "npm run build",
150
- "test:smoke": "node scripts/smoke-installed.mjs"
154
+ "test:smoke": "node scripts/smoke-installed.mjs",
155
+ "test:mutation": "node scripts/mutation-audit.mjs"
151
156
  },
152
157
  "main": "./dist/index.js",
153
158
  "types": "./dist/index.d.ts",
@@ -51,7 +51,8 @@ export interface PromptRequest {
51
51
  * breaking) cannot recover that from `scope` or from parsing `reason`. Only "declined" means a human said
52
52
  * no; the other three are absence-of-signal, not a signal.
53
53
  */
54
- export type PromptOutcomeKind = "granted" | "no-ui" | "declined" | "dismissed" | "error";
54
+ export const PROMPT_OUTCOME_KINDS = ["granted", "declined", "dismissed", "no-ui", "error"] as const;
55
+ export type PromptOutcomeKind = typeof PROMPT_OUTCOME_KINDS[number];
55
56
 
56
57
  export interface PromptOutcome {
57
58
  /** The scope the human chose, or null for any form of no. */
package/src/approval.ts CHANGED
@@ -14,10 +14,12 @@ import type { Capability, ResolveResult } from "./resolve.ts";
14
14
  import { approvalBindingsEqual, type ApprovalBinding } from "./correlation.ts";
15
15
 
16
16
  /** How far a single yes reaches in time. */
17
- export type ApprovalScope = "once" | "session" | "always";
17
+ export const APPROVAL_SCOPES = ["once", "session", "always"] as const;
18
+ export type ApprovalScope = typeof APPROVAL_SCOPES[number];
18
19
 
19
20
  /** Where a yes came from, for the ledger. These call for different follow-ups, so they stay distinct. */
20
- export type ApprovalSource = "prompt" | "session" | "persisted" | "inherited";
21
+ export const APPROVAL_SOURCES = ["prompt", "session", "persisted", "inherited"] as const;
22
+ export type ApprovalSource = typeof APPROVAL_SOURCES[number];
21
23
 
22
24
  /** Which call site is asking. Determines the scopes offered — see `offeredScopes`. */
23
25
  export type ApprovalPath = "definition" | "delegate";
@@ -12,8 +12,9 @@
12
12
  * *what one delegation does*.
13
13
  */
14
14
 
15
- import { AGENT_WILDCARD, type Capability } from "./resolve.ts";
15
+ import { AGENT_WILDCARD, WORKSPACE_WILDCARD, type Capability } from "./resolve.ts";
16
16
  import { WILDCARD } from "./pi-tools.ts";
17
+ import { GovernanceRefusal, refusal } from "./refusals.ts";
17
18
 
18
19
  /** The capability that authorises spawning a definition (ADR-0017). `tool:*` satisfies any of them. */
19
20
  export const agentCapability = (name: string): Capability => `agent:${name}`;
@@ -41,11 +42,179 @@ export function maySpawnDefinition(ownGrant: Capability[], name: string): boolea
41
42
  /** The tool name that confers the ability to delegate further. */
42
43
  export const DELEGATE_CAPABILITY: Capability = "tool:delegate";
43
44
 
45
+ /**
46
+ * Every namespace a capability id may carry, as its literal prefix.
47
+ *
48
+ * **One list, because there were three and adding a namespace only updated some of them.** ADR-0035 taught
49
+ * `normaliseCapability` about `workspace:` and left `ceilingForDefinition` (which mangled it into
50
+ * `tool:workspace:<id>`) and `isSafeCapability` (which called it malformed) behind. The two places that parse
51
+ * an id's namespace read this, so a fifth namespace is one entry rather than a third divergence.
52
+ *
53
+ * **Not every prefix decision in the package.** Inline `startsWith("` tests on a capability namespace are
54
+ * scattered across `src/` and `extensions/`; this list is read by two of them. **No number is written here,
55
+ * and the history is why:** SPEC first claimed the list was "what every site reads"; a correction said "six"
56
+ * while its own enumeration listed seven; this docstring then said "nine" in the sentence claiming it gave no
57
+ * number; and the correction to *that* said "fourteen", which counted every file containing any
58
+ * `startsWith("` — CLI flags, `git worktree list` parsing, an assurance sentinel and node_modules entries
59
+ * among them. Four numbers, four wrong. The command, so nobody has to quote a predecessor:
60
+ *
61
+ * grep -rl 'startsWith("\(tool:\|ext:\|agent:\|skill:\|workspace:\)' src extensions
62
+ *
63
+ * Consolidating the sites is a separate change, and a count nobody re-derives is the defect this list exists
64
+ * to prevent.
65
+ *
66
+ * `docs/SPEC.md`'s grammar section is the prose statement of the same list and is kept in step with it.
67
+ */
68
+ export const CAPABILITY_NAMESPACE_PREFIXES = ["tool:", "ext:", "skill:", "agent:", "workspace:"] as const;
69
+
44
70
  /** Accept `read` or `tool:read` or `ext:pkg/tool` and normalise to a capability id. */
45
71
  export function normaliseCapability(raw: string): Capability {
46
72
  const value = raw.trim();
47
- if (value.startsWith("tool:") || value.startsWith("ext:") || value.startsWith("skill:") || value.startsWith("agent:")) {
48
- return value;
49
- }
73
+ if (CAPABILITY_NAMESPACE_PREFIXES.some((prefix) => value.startsWith(prefix))) return value;
50
74
  return `tool:${value}`;
51
75
  }
76
+
77
+ /** The capability that authorises routing a child to one registered workspace (ADR-0035). */
78
+ export function workspaceCapability(workspaceId: string): Capability {
79
+ return `workspace:${workspaceId}`;
80
+ }
81
+
82
+ /**
83
+ * May this session route a child to workspace `id`?
84
+ *
85
+ * Mirrors `maySpawnDefinition` deliberately — same shape, same wildcard handling, same reason. Routing was
86
+ * the one governance dimension that did not attenuate (R-131, measured in
87
+ * `docs/probes/g36-workspace-attenuation`): the registry inherited into every child and nothing checked the
88
+ * caller's authority, so a child routed to `staging` could route its grandchild to `prod`.
89
+ *
90
+ * `tool:*` satisfies it because governance is opt-in — an ungoverned session holds the wildcard and must
91
+ * keep working exactly as before.
92
+ */
93
+ export function mayRouteToWorkspace(ownGrant: readonly Capability[], workspaceId: string): boolean {
94
+ const held = new Set(ownGrant);
95
+ return held.has(WILDCARD)
96
+ || held.has(WORKSPACE_WILDCARD)
97
+ || held.has(workspaceCapability(workspaceId));
98
+ }
99
+
100
+ /**
101
+ * Characters that make a capability id mean something OTHER than one capability.
102
+ *
103
+ * A capability id is never a list. `PI_GRANTS_GRANT` is comma-separated and `parseList` trims and splits
104
+ * it, so a comma inside a single id means the child reads it as several — including ones nothing granted.
105
+ * Newlines are here for the same reason one level out: the grant travels through an environment variable
106
+ * and, via `assertGrantIsWritable`, a shell-sourced file.
107
+ *
108
+ * **Measured, on the published 0.18.0.** A root holding `agent:*` (or `tool:*`) that requested
109
+ * `agent:x,tool:bash` had it admitted by the wildcard's prefix rule, written verbatim into the child's
110
+ * grant, and split by the child into `agent:x` + `tool:bash` — minting a capability the root never held,
111
+ * with `denied: []` so nothing recorded an escalation and the ledger line looked clean.
112
+ *
113
+ * Deliberately a blocklist of structurally dangerous characters rather than the full grammar whitelist in
114
+ * `isSafeCapability`. That whitelist is right where it lives — the boundary that GENERATES a grant — but
115
+ * this is the enforcement path, which must keep accepting whatever ids operators already have. A security
116
+ * patch to a released version should close the hole without inventing a new way to refuse a legitimate
117
+ * setup. `grant-env.ts` predicted this channel: "the third channel — whatever it turns out to be — should
118
+ * cost a refusal rather than an injection."
119
+ */
120
+ const CAPABILITY_ID_SEPARATORS = /[,\r\n\0]/;
121
+
122
+ /** False when an id would be read as more than one capability by any channel that carries it. */
123
+ export function isWellFormedCapability(id: string): boolean {
124
+ return id.length > 0 && !CAPABILITY_ID_SEPARATORS.test(id) && id.trim() === id;
125
+ }
126
+
127
+ /**
128
+ * The STRICT grammar: a whitelist of what a capability id may look like, not a blocklist of what it may not.
129
+ *
130
+ * **Two consumers, and they are two different reasons for the same rule.** It lives here rather than in
131
+ * either of them because it shipped in one and was needed by both:
132
+ *
133
+ * 1. `skill-packages.ts` — the boundary that GENERATES a grant. R-77/R-78: a package declaring
134
+ * `allowed-tools: Read,ext:x";touch /tmp/pwned;PI_GRANTS_GRANT="` produced a `.pi/grants.env` that ran
135
+ * arbitrary code the moment an operator sourced the line `init` prints.
136
+ * 2. `workspace.ts` — the operator registry, since ADR-0035 made a registry id the tail of a capability id
137
+ * (`workspace:<id>`). That made the registry an input to this grammar, and it got the LOOSE
138
+ * `isWellFormedCapability` blocklist instead, which review showed costs three things:
139
+ * an id of literally `*` loaded and minted `WORKSPACE_WILDCARD`, so an operator naming ONE worktree held
140
+ * routing authority over every id in the registry including ones added later;
141
+ * an id containing a space became TWO capabilities, because `ceilingForDefinition` splits `allowed-tools`
142
+ * on `[\s,]+` — `allowed-tools: read, workspace:prod bash` measured as
143
+ * `['tool:bash','tool:read','workspace:prod']`, i.e. routing over production plus a shell, neither typed
144
+ * by anyone (0.18.1's comma, one namespace over);
145
+ * and quote/`$()`/backtick ids reached the `ROUTABLE WORKSPACES` block of the generated file, whose own
146
+ * instructions tell the operator to paste them into `PI_GRANTS_GRANT`.
147
+ *
148
+ * **A blocklist was the wrong shape for this and the comment on `isWellFormedCapability` says why it is the
149
+ * right shape THERE**: that one guards the enforcement path, which must keep accepting whatever ids operators
150
+ * already have. A registry is a file the operator writes and can rename, so refusing a hostile id costs
151
+ * nothing — and `grant-env.ts`'s standing warning applies to it exactly: *"the third channel — whatever it
152
+ * turns out to be — should cost a refusal rather than an injection."* The registry was the third channel.
153
+ */
154
+ /**
155
+ * A workspace registry id, which is NOT a tool name and needed its own rule.
156
+ *
157
+ * `isSafeCapability` was reused here first, and that was the error: it is the grammar for a *tool* name, and
158
+ * it refused ids that published 0.18.1 accepted — `feature/x` above all. Git worktrees are routinely named
159
+ * after their branch, so a slash is the ordinary case, and a slash splits nothing: not the comma-separated
160
+ * grant, not `allowed-tools`' `[\s,]+`. Refusing it bought no safety and broke the common setup.
161
+ *
162
+ * What is refused, and why each one earns it:
163
+ * - **whitespace** — `ceilingForDefinition` splits `allowed-tools` on `[\s,]+`, so `workspace:prod bash`
164
+ * measured as `['tool:bash','tool:read','workspace:prod']`: routing over production plus a shell, neither
165
+ * typed by anyone. 0.18.1's comma, one namespace over.
166
+ * - **comma, CR, LF, NUL** — split a grant.
167
+ * - **`*`** — collides with `WORKSPACE_WILDCARD`, so registering a worktree as `*` and granting
168
+ * `workspace:*` believing it named that one root minted routing authority over the whole registry.
169
+ * - **quotes, `$`, backticks, `;`, `&`, `|`, `<`, `>`, `(`, `)`, `\`, `#`** — reach the ROUTABLE WORKSPACES
170
+ * block of a generated `.pi/grants.env`, whose own instructions tell the operator to paste the id into
171
+ * `PI_GRANTS_GRANT`. Sourcing the file was safe; following its instructions executed. R-77/R-78.
172
+ * - **control characters and non-ASCII** — the generated file is reviewed in an editor and `/grants` prints
173
+ * these; backspace and ANSI escapes let one id render as another. This is the one refusal that costs a
174
+ * legitimate user something (a non-English worktree name), and it is a deliberate trade recorded as
175
+ * breaking rather than asserted to cost nothing.
176
+ */
177
+ export function isSafeWorkspaceId(id: string): boolean {
178
+ return /^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(id);
179
+ }
180
+
181
+ export function isSafeCapability(id: Capability): boolean {
182
+ const segment = "[A-Za-z0-9][A-Za-z0-9._-]*";
183
+ // `workspace:` delegates to the WORKSPACE grammar rather than reusing `segment`, and that is the fifth
184
+ // site of the same defect. `isSafeWorkspaceId` allows a slash because a git worktree is routinely named
185
+ // after its branch; `segment` does not, so this function refused `workspace:feature/x` — dropping the whole
186
+ // definition, exiting 1, and telling the operator "a capability id is tool:/skill:/agent:<name> or
187
+ // ext:<pkg>/<tool>", which does not even mention the namespace. Meanwhile the CHANGELOG told them slashes
188
+ // are fine. **The boundary that GENERATES grants could not emit the id shape the release advertises**, so
189
+ // the migration path for a breaking change was closed by the commit that documented it as open.
190
+ if (id.startsWith("workspace:")) return isSafeWorkspaceId(id.slice("workspace:".length));
191
+ return (
192
+ new RegExp(`^(tool|skill|agent):${segment}$`).test(id) ||
193
+ new RegExp(`^ext:(@${segment}/)?${segment}/${segment}$`).test(id)
194
+ );
195
+ }
196
+
197
+ /**
198
+ * The write-side backstop, for every channel that joins capabilities into one string.
199
+ *
200
+ * `covered()` already refuses to GRANT a malformed id, so this should be unreachable — which is exactly
201
+ * why it exists. `grant-env.ts` says it better than I can: "the third channel — whatever it turns out to
202
+ * be — should cost a refusal rather than an injection. A guard that depends on my enumeration being
203
+ * complete is not a guard." The propagation channel turned out to be that third channel, and it had no
204
+ * backstop while the file-writing channel did.
205
+ *
206
+ * Loud rather than silently filtered: a malformed id here means something upstream admitted one, and
207
+ * dropping it quietly would hide the defect that produced it.
208
+ */
209
+ export function assertCapabilitiesArePropagatable(grant: readonly Capability[]): void {
210
+ const malformed = grant.filter((c) => !isWellFormedCapability(c));
211
+ if (malformed.length === 0) return;
212
+ throw new GovernanceRefusal(refusal(
213
+ "GRANT_ID_MALFORMED",
214
+ `refusing to build a child environment: ${JSON.stringify(malformed)} contain characters that are not ` +
215
+ `part of a capability id. The grant is comma-separated, so a child would read these as several ` +
216
+ `capabilities — including ones nothing granted. Report this: a malformed id reached propagation, ` +
217
+ `which means a guard upstream admitted it.`,
218
+ { malformed: malformed.join(" ") },
219
+ ));
220
+ }