pi-daddy 0.18.1 → 0.20.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 (218) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +136 -23
  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/contracts/ledger/v3/README.md +36 -0
  10. package/contracts/ledger/v3/fixtures/capability-decision.json +98 -0
  11. package/contracts/ledger/v3/fixtures/check-receipt.json +42 -0
  12. package/contracts/ledger/v3/fixtures/child-lifecycle.json +45 -0
  13. package/contracts/ledger/v3/fixtures/workflow-fact.json +41 -0
  14. package/contracts/ledger/v3/fixtures/workspace-lease.json +43 -0
  15. package/contracts/ledger/v3/ledger-event.schema.json +930 -0
  16. package/dist/approval-prompt.d.ts +2 -1
  17. package/dist/approval-prompt.d.ts.map +1 -1
  18. package/dist/approval-prompt.js +9 -0
  19. package/dist/approval-prompt.js.map +1 -1
  20. package/dist/approval.d.ts +4 -2
  21. package/dist/approval.d.ts.map +1 -1
  22. package/dist/approval.js +4 -0
  23. package/dist/approval.js.map +1 -1
  24. package/dist/capabilities.d.ts +90 -0
  25. package/dist/capabilities.d.ts.map +1 -1
  26. package/dist/capabilities.js +114 -3
  27. package/dist/capabilities.js.map +1 -1
  28. package/dist/catalog.d.ts +15 -1
  29. package/dist/catalog.d.ts.map +1 -1
  30. package/dist/catalog.js +54 -3
  31. package/dist/catalog.js.map +1 -1
  32. package/dist/check-runner.d.ts.map +1 -1
  33. package/dist/check-runner.js +19 -7
  34. package/dist/check-runner.js.map +1 -1
  35. package/dist/cli.d.ts.map +1 -1
  36. package/dist/cli.js +21 -1
  37. package/dist/cli.js.map +1 -1
  38. package/dist/correlation.d.ts.map +1 -1
  39. package/dist/correlation.js +10 -0
  40. package/dist/correlation.js.map +1 -1
  41. package/dist/dashboard-cli.d.ts +18 -0
  42. package/dist/dashboard-cli.d.ts.map +1 -0
  43. package/dist/dashboard-cli.js +154 -0
  44. package/dist/dashboard-cli.js.map +1 -0
  45. package/dist/dashboard-handshake.d.ts +37 -0
  46. package/dist/dashboard-handshake.d.ts.map +1 -0
  47. package/dist/dashboard-handshake.js +127 -0
  48. package/dist/dashboard-handshake.js.map +1 -0
  49. package/dist/dashboard-herdr.d.ts +54 -0
  50. package/dist/dashboard-herdr.d.ts.map +1 -0
  51. package/dist/dashboard-herdr.js +286 -0
  52. package/dist/dashboard-herdr.js.map +1 -0
  53. package/dist/dashboard-projection.d.ts +73 -0
  54. package/dist/dashboard-projection.d.ts.map +1 -0
  55. package/dist/dashboard-projection.js +294 -0
  56. package/dist/dashboard-projection.js.map +1 -0
  57. package/dist/dashboard-render.d.ts +10 -0
  58. package/dist/dashboard-render.d.ts.map +1 -0
  59. package/dist/dashboard-render.js +208 -0
  60. package/dist/dashboard-render.js.map +1 -0
  61. package/dist/definitions.d.ts.map +1 -1
  62. package/dist/definitions.js +7 -1
  63. package/dist/definitions.js.map +1 -1
  64. package/dist/delegate-types.d.ts +6 -2
  65. package/dist/delegate-types.d.ts.map +1 -1
  66. package/dist/delegate-types.js.map +1 -1
  67. package/dist/delegate.d.ts.map +1 -1
  68. package/dist/delegate.js +34 -4
  69. package/dist/delegate.js.map +1 -1
  70. package/dist/delegation-approval.d.ts.map +1 -1
  71. package/dist/delegation-approval.js +37 -12
  72. package/dist/delegation-approval.js.map +1 -1
  73. package/dist/execution-id.d.ts +6 -0
  74. package/dist/execution-id.d.ts.map +1 -0
  75. package/dist/execution-id.js +13 -0
  76. package/dist/execution-id.js.map +1 -0
  77. package/dist/executor.d.ts +2 -1
  78. package/dist/executor.d.ts.map +1 -1
  79. package/dist/executor.js +1 -0
  80. package/dist/executor.js.map +1 -1
  81. package/dist/grant-env.d.ts +2 -0
  82. package/dist/grant-env.d.ts.map +1 -1
  83. package/dist/grant-env.js +26 -3
  84. package/dist/grant-env.js.map +1 -1
  85. package/dist/index.d.ts +4 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +4 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/init.d.ts +13 -1
  90. package/dist/init.d.ts.map +1 -1
  91. package/dist/init.js +35 -2
  92. package/dist/init.js.map +1 -1
  93. package/dist/lease-helper.d.ts +58 -0
  94. package/dist/lease-helper.d.ts.map +1 -0
  95. package/dist/lease-helper.js +94 -0
  96. package/dist/lease-helper.js.map +1 -0
  97. package/dist/lease-record.d.ts +15 -3
  98. package/dist/lease-record.d.ts.map +1 -1
  99. package/dist/lease-record.js.map +1 -1
  100. package/dist/ledger-events.d.ts +59 -15
  101. package/dist/ledger-events.d.ts.map +1 -1
  102. package/dist/ledger-events.js +67 -5
  103. package/dist/ledger-events.js.map +1 -1
  104. package/dist/ledger-identifiers.d.ts +7 -0
  105. package/dist/ledger-identifiers.d.ts.map +1 -0
  106. package/dist/ledger-identifiers.js +20 -0
  107. package/dist/ledger-identifiers.js.map +1 -0
  108. package/dist/ledger-report.d.ts +5 -2
  109. package/dist/ledger-report.d.ts.map +1 -1
  110. package/dist/ledger-report.js +38 -14
  111. package/dist/ledger-report.js.map +1 -1
  112. package/dist/ledger-v3-validation.d.ts +11 -0
  113. package/dist/ledger-v3-validation.d.ts.map +1 -0
  114. package/dist/ledger-v3-validation.js +265 -0
  115. package/dist/ledger-v3-validation.js.map +1 -0
  116. package/dist/ledger.d.ts +27 -9
  117. package/dist/ledger.d.ts.map +1 -1
  118. package/dist/ledger.js +38 -6
  119. package/dist/ledger.js.map +1 -1
  120. package/dist/propagation.d.ts +19 -1
  121. package/dist/propagation.d.ts.map +1 -1
  122. package/dist/propagation.js +23 -1
  123. package/dist/propagation.js.map +1 -1
  124. package/dist/refusals.d.ts +1 -1
  125. package/dist/refusals.d.ts.map +1 -1
  126. package/dist/refusals.js +1 -0
  127. package/dist/refusals.js.map +1 -1
  128. package/dist/resolve.d.ts +10 -0
  129. package/dist/resolve.d.ts.map +1 -1
  130. package/dist/resolve.js +27 -3
  131. package/dist/resolve.js.map +1 -1
  132. package/dist/routing-authority.d.ts +71 -0
  133. package/dist/routing-authority.d.ts.map +1 -0
  134. package/dist/routing-authority.js +100 -0
  135. package/dist/routing-authority.js.map +1 -0
  136. package/dist/run-child.d.ts +3 -1
  137. package/dist/run-child.d.ts.map +1 -1
  138. package/dist/run-child.js +30 -2
  139. package/dist/run-child.js.map +1 -1
  140. package/dist/run-herdr.d.ts +2 -0
  141. package/dist/run-herdr.d.ts.map +1 -1
  142. package/dist/run-herdr.js +8 -0
  143. package/dist/run-herdr.js.map +1 -1
  144. package/dist/skill-packages.d.ts +11 -5
  145. package/dist/skill-packages.d.ts.map +1 -1
  146. package/dist/skill-packages.js +20 -11
  147. package/dist/skill-packages.js.map +1 -1
  148. package/dist/workflow-fact-id.d.ts +4 -0
  149. package/dist/workflow-fact-id.d.ts.map +1 -0
  150. package/dist/workflow-fact-id.js +14 -0
  151. package/dist/workflow-fact-id.js.map +1 -0
  152. package/dist/workflow-facts.d.ts +34 -0
  153. package/dist/workflow-facts.d.ts.map +1 -0
  154. package/dist/workflow-facts.js +43 -0
  155. package/dist/workflow-facts.js.map +1 -0
  156. package/dist/workspace-lease.d.ts +15 -3
  157. package/dist/workspace-lease.d.ts.map +1 -1
  158. package/dist/workspace-lease.js +81 -24
  159. package/dist/workspace-lease.js.map +1 -1
  160. package/dist/workspace.d.ts +25 -0
  161. package/dist/workspace.d.ts.map +1 -1
  162. package/dist/workspace.js +142 -5
  163. package/dist/workspace.js.map +1 -1
  164. package/extensions/chain-ledger.ts +4 -0
  165. package/extensions/chain-plan.ts +98 -0
  166. package/extensions/delegate-chain.ts +63 -122
  167. package/extensions/delegation-ledger.ts +60 -0
  168. package/extensions/delegation.ts +20 -14
  169. package/extensions/execute-child.ts +85 -21
  170. package/extensions/execution-occurrence.ts +20 -0
  171. package/extensions/grants-command.ts +33 -4
  172. package/extensions/grants.ts +50 -1
  173. package/extensions/init-command.ts +33 -2
  174. package/extensions/run-delegation.ts +36 -56
  175. package/extensions/session-report.ts +13 -20
  176. package/extensions/session.ts +16 -3
  177. package/extensions/workspace-runtime.ts +44 -4
  178. package/herdr-plugin/herdr-plugin.toml +18 -0
  179. package/package.json +22 -5
  180. package/src/approval-prompt.ts +2 -1
  181. package/src/approval.ts +4 -2
  182. package/src/capabilities.ts +120 -4
  183. package/src/catalog.ts +62 -4
  184. package/src/check-runner.ts +24 -8
  185. package/src/cli.ts +24 -1
  186. package/src/correlation.ts +11 -0
  187. package/src/dashboard-cli.ts +171 -0
  188. package/src/dashboard-handshake.ts +185 -0
  189. package/src/dashboard-herdr.ts +355 -0
  190. package/src/dashboard-projection.ts +390 -0
  191. package/src/dashboard-render.ts +254 -0
  192. package/src/definitions.ts +7 -1
  193. package/src/delegate-types.ts +6 -2
  194. package/src/delegate.ts +43 -4
  195. package/src/delegation-approval.ts +37 -12
  196. package/src/execution-id.ts +18 -0
  197. package/src/executor.ts +2 -1
  198. package/src/grant-env.ts +39 -7
  199. package/src/index.ts +26 -0
  200. package/src/init.ts +40 -2
  201. package/src/lease-helper.ts +97 -0
  202. package/src/lease-record.ts +15 -3
  203. package/src/ledger-events.ts +116 -28
  204. package/src/ledger-identifiers.ts +21 -0
  205. package/src/ledger-report.ts +40 -19
  206. package/src/ledger-v3-validation.ts +266 -0
  207. package/src/ledger.ts +75 -12
  208. package/src/propagation.ts +24 -1
  209. package/src/refusals.ts +1 -0
  210. package/src/resolve.ts +29 -3
  211. package/src/routing-authority.ts +121 -0
  212. package/src/run-child.ts +35 -3
  213. package/src/run-herdr.ts +9 -0
  214. package/src/skill-packages.ts +20 -13
  215. package/src/workflow-fact-id.ts +16 -0
  216. package/src/workflow-facts.ts +65 -0
  217. package/src/workspace-lease.ts +84 -24
  218. package/src/workspace.ts +179 -6
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The v2 runtime-event half of the ledger: workspace leases, child lifecycle and check receipts, with
2
+ * The v3 runtime-event half of the ledger: workspace leases, child lifecycle and check receipts, with
3
3
  * their builders. Split out of `./ledger.ts` only to stay under the 400-line module ceiling this
4
4
  * project enforces mechanically; `./ledger.ts` re-exports everything here, so "the ledger module"
5
5
  * remains one import.
@@ -8,6 +8,27 @@ import { LEDGER_VERSION, type LedgerEventBase, type GrantRecord } from "./ledger
8
8
  import type { ExecutorKind } from "./executor.ts";
9
9
  import type { CorrelationMetadata } from "./correlation.ts";
10
10
  import type { StructuredRefusal } from "./refusals.ts";
11
+ import { assertExecutionId } from "./execution-id.ts";
12
+ import type { WorkflowFactEvent } from "./workflow-facts.ts";
13
+ import { assertLedgerV3Wire } from "./ledger-v3-validation.ts";
14
+
15
+ export const WORKSPACE_ACCESSES = ["read", "write"] as const;
16
+ export type WorkspaceAccess = typeof WORKSPACE_ACCESSES[number];
17
+
18
+ export const WORKSPACE_RECOVERY_VALUES = [false, true, "unknown"] as const;
19
+ export type WorkspaceRecovery = typeof WORKSPACE_RECOVERY_VALUES[number];
20
+
21
+ export const CHILD_LIFECYCLE_STATES = ["starting", "running", "completed", "failed"] as const;
22
+ export type ChildLifecycleState = typeof CHILD_LIFECYCLE_STATES[number];
23
+
24
+ /** The complete Node signal vocabulary accepted by a v3 child-lifecycle event. */
25
+ export const CHILD_PROCESS_SIGNALS = [
26
+ "SIGABRT", "SIGALRM", "SIGBUS", "SIGCHLD", "SIGCONT", "SIGFPE", "SIGHUP", "SIGILL", "SIGINT", "SIGIO",
27
+ "SIGIOT", "SIGKILL", "SIGPIPE", "SIGPOLL", "SIGPROF", "SIGPWR", "SIGQUIT", "SIGSEGV", "SIGSTKFLT",
28
+ "SIGSTOP", "SIGSYS", "SIGTERM", "SIGTRAP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGUNUSED", "SIGURG",
29
+ "SIGUSR1", "SIGUSR2", "SIGVTALRM", "SIGWINCH", "SIGXCPU", "SIGXFSZ", "SIGBREAK", "SIGLOST", "SIGINFO",
30
+ ] as const satisfies readonly NodeJS.Signals[];
31
+ export type ChildProcessSignal = typeof CHILD_PROCESS_SIGNALS[number];
11
32
 
12
33
  /**
13
34
  * `released` is a handover this owner performed. FOUR members were added by the 0.18.0 review pass, and
@@ -20,27 +41,24 @@ import type { StructuredRefusal } from "./refusals.ts";
20
41
  * `uncontended` a read lease took no kernel lock at all, so counting it as an acquisition
21
42
  * overstated how many exclusions the kernel actually performed (R-105).
22
43
  */
23
- export type WorkspaceLeaseOutcome =
24
- | "acquired"
25
- | "uncontended"
26
- | "refused"
27
- | "released"
28
- | "released-unrecorded"
29
- | "lost"
30
- | "retained"
31
- | "timeout"
32
- | "recovered";
44
+ export const WORKSPACE_LEASE_OUTCOMES = [
45
+ "acquired", "uncontended", "refused", "released", "released-unrecorded", "lost", "retained", "timeout",
46
+ "recovered",
47
+ ] as const;
48
+ export type WorkspaceLeaseOutcome = typeof WORKSPACE_LEASE_OUTCOMES[number];
33
49
 
34
50
  export interface WorkspaceLeaseEvent extends LedgerEventBase {
35
51
  ledgerVersion: typeof LEDGER_VERSION;
36
52
  event: "workspace_lease";
53
+ executionId: string;
54
+ parentExecutionId: string | null;
37
55
  childId: string;
38
56
  workspaceId: string;
39
57
  root: string;
40
- access: "read" | "write";
58
+ access: WorkspaceAccess;
41
59
  outcome: WorkspaceLeaseOutcome;
42
60
  /** `"unknown"` when the prior owner's record was unreadable — not evidence of a clean handover. */
43
- recovered?: boolean | "unknown";
61
+ recovered?: WorkspaceRecovery;
44
62
  releaseReason?: string;
45
63
  refusal?: StructuredRefusal;
46
64
  }
@@ -48,20 +66,29 @@ export interface WorkspaceLeaseEvent extends LedgerEventBase {
48
66
  export interface ChildLifecycleEvent extends LedgerEventBase {
49
67
  ledgerVersion: typeof LEDGER_VERSION;
50
68
  event: "child_lifecycle";
69
+ executionId: string;
70
+ parentExecutionId: string | null;
51
71
  childId: string;
52
- state: "starting" | "completed" | "failed";
72
+ state: ChildLifecycleState;
53
73
  executor: ExecutorKind;
74
+ /** Bound after which a non-terminal start is known to be incomplete. */
75
+ deadlineAt?: string;
76
+ /** Herdr runtime identity is observational and never an enforcement boundary. */
77
+ herdrPaneId?: string;
78
+ herdrAgentName?: string;
54
79
  exitCode?: number | null;
55
- signal?: NodeJS.Signals | null;
56
- timedOut?: boolean;
57
- aborted?: boolean;
58
- truncated?: boolean;
80
+ signal?: ChildProcessSignal | null;
81
+ timedOut?: true;
82
+ aborted?: true;
83
+ truncated?: true;
59
84
  reason?: string;
60
85
  }
61
86
 
62
87
  export interface CheckReceiptLedgerEvent extends LedgerEventBase {
63
88
  ledgerVersion: typeof LEDGER_VERSION;
64
89
  event: "check_receipt";
90
+ executionId: string;
91
+ parentExecutionId: string | null;
65
92
  childId: string;
66
93
  receiptId: string;
67
94
  workspaceId: string;
@@ -69,28 +96,42 @@ export interface CheckReceiptLedgerEvent extends LedgerEventBase {
69
96
  treeSha: string;
70
97
  }
71
98
 
99
+ export type CapabilityDecisionEvent = GrantRecord & {
100
+ ledgerVersion: typeof LEDGER_VERSION;
101
+ event: "capability_decision";
102
+ executionId: string;
103
+ parentExecutionId: string | null;
104
+ taskDigest: string;
105
+ };
106
+
72
107
  export type RuntimeLedgerEvent =
73
- | (GrantRecord & { ledgerVersion: typeof LEDGER_VERSION; event: "capability_decision" })
108
+ | CapabilityDecisionEvent
74
109
  | WorkspaceLeaseEvent
75
110
  | ChildLifecycleEvent
76
- | CheckReceiptLedgerEvent;
111
+ | CheckReceiptLedgerEvent
112
+ | WorkflowFactEvent;
77
113
 
78
114
  export function buildWorkspaceLeaseEvent(args: {
115
+ executionId: string;
116
+ parentExecutionId: string | null;
79
117
  childId: string;
80
118
  workspaceId: string;
81
119
  root: string;
82
- access: "read" | "write";
120
+ access: WorkspaceAccess;
83
121
  outcome: WorkspaceLeaseOutcome;
84
- recovered?: boolean | "unknown";
122
+ recovered?: WorkspaceRecovery;
85
123
  releaseReason?: string;
86
124
  refusal?: StructuredRefusal;
87
125
  correlation?: CorrelationMetadata;
88
126
  now: Date;
89
127
  }): WorkspaceLeaseEvent {
90
- return {
128
+ assertEventIdentity(args);
129
+ return assertLedgerV3Wire({
91
130
  ledgerVersion: LEDGER_VERSION,
92
131
  event: "workspace_lease",
93
132
  ts: args.now.toISOString(),
133
+ executionId: args.executionId,
134
+ parentExecutionId: args.parentExecutionId,
94
135
  childId: args.childId,
95
136
  workspaceId: args.workspaceId,
96
137
  root: args.root,
@@ -104,15 +145,50 @@ export function buildWorkspaceLeaseEvent(args: {
104
145
  ...(args.releaseReason ? { releaseReason: args.releaseReason } : {}),
105
146
  ...(args.refusal ? { refusal: structuredClone(args.refusal) } : {}),
106
147
  ...(args.correlation ? { correlation: structuredClone(args.correlation) } : {}),
107
- };
148
+ });
149
+ }
150
+
151
+ export function buildCheckReceiptLedgerEvent(args: {
152
+ executionId: string;
153
+ parentExecutionId: string | null;
154
+ childId: string;
155
+ receiptId: string;
156
+ workspaceId: string;
157
+ checkId: string;
158
+ treeSha: string;
159
+ correlation?: CorrelationMetadata;
160
+ now: Date;
161
+ }): CheckReceiptLedgerEvent {
162
+ assertEventIdentity(args);
163
+ if (!/^[a-f0-9]{64}$/i.test(args.receiptId)) {
164
+ throw new TypeError("receiptId must be a SHA-256 hex digest");
165
+ }
166
+ return assertLedgerV3Wire({
167
+ ledgerVersion: LEDGER_VERSION,
168
+ event: "check_receipt",
169
+ ts: args.now.toISOString(),
170
+ executionId: args.executionId,
171
+ parentExecutionId: args.parentExecutionId,
172
+ childId: args.childId,
173
+ receiptId: args.receiptId,
174
+ workspaceId: args.workspaceId,
175
+ checkId: args.checkId,
176
+ treeSha: args.treeSha,
177
+ ...(args.correlation ? { correlation: structuredClone(args.correlation) } : {}),
178
+ });
108
179
  }
109
180
 
110
181
  export function buildChildLifecycleEvent(args: {
182
+ executionId: string;
183
+ parentExecutionId: string | null;
111
184
  childId: string;
112
- state: "starting" | "completed" | "failed";
185
+ state: ChildLifecycleState;
113
186
  executor: ExecutorKind;
187
+ deadlineAt?: string;
188
+ herdrPaneId?: string;
189
+ herdrAgentName?: string;
114
190
  exitCode?: number | null;
115
- signal?: NodeJS.Signals | null;
191
+ signal?: ChildProcessSignal | null;
116
192
  timedOut?: boolean;
117
193
  aborted?: boolean;
118
194
  truncated?: boolean;
@@ -120,13 +196,19 @@ export function buildChildLifecycleEvent(args: {
120
196
  correlation?: CorrelationMetadata;
121
197
  now: Date;
122
198
  }): ChildLifecycleEvent {
123
- return {
199
+ assertEventIdentity(args);
200
+ return assertLedgerV3Wire({
124
201
  ledgerVersion: LEDGER_VERSION,
125
202
  event: "child_lifecycle",
126
203
  ts: args.now.toISOString(),
204
+ executionId: args.executionId,
205
+ parentExecutionId: args.parentExecutionId,
127
206
  childId: args.childId,
128
207
  state: args.state,
129
208
  executor: args.executor,
209
+ ...(args.deadlineAt ? { deadlineAt: args.deadlineAt } : {}),
210
+ ...(args.herdrPaneId ? { herdrPaneId: args.herdrPaneId } : {}),
211
+ ...(args.herdrAgentName ? { herdrAgentName: args.herdrAgentName } : {}),
130
212
  ...(args.exitCode !== undefined ? { exitCode: args.exitCode } : {}),
131
213
  ...(args.signal !== undefined ? { signal: args.signal } : {}),
132
214
  ...(args.timedOut ? { timedOut: true } : {}),
@@ -134,5 +216,11 @@ export function buildChildLifecycleEvent(args: {
134
216
  ...(args.truncated ? { truncated: true } : {}),
135
217
  ...(args.reason ? { reason: args.reason } : {}),
136
218
  ...(args.correlation ? { correlation: structuredClone(args.correlation) } : {}),
137
- };
219
+ });
220
+ }
221
+
222
+ function assertEventIdentity(args: { executionId: string; parentExecutionId: string | null }): void {
223
+ assertExecutionId(args.executionId);
224
+ if (args.parentExecutionId !== null) assertExecutionId(args.parentExecutionId, "parentExecutionId");
225
+ if (args.parentExecutionId === args.executionId) throw new TypeError("an execution cannot be its own parent");
138
226
  }
@@ -0,0 +1,21 @@
1
+ const DISPLAY_IDENTIFIER = /^[A-Za-z0-9@*][A-Za-z0-9@*._:/-]{0,511}$/;
2
+ const CORRELATION_IDENTIFIER = /^[A-Za-z0-9@*][A-Za-z0-9@*._:/-]{0,127}$/;
3
+ const CAPABILITY_TAIL = /^[A-Za-z0-9@*][A-Za-z0-9@*._/-]{0,255}$/;
4
+ const CAPABILITY_PREFIXES = ["tool:", "skill:", "agent:", "workspace:", "ext:"] as const;
5
+
6
+ /** Identifier text safe to copy onto the operator's terminal; prose belongs in no ledger display field. */
7
+ export function isLedgerDisplayIdentifier(value: unknown): value is string {
8
+ return typeof value === "string" && DISPLAY_IDENTIFIER.test(value);
9
+ }
10
+
11
+ /** The tighter bound for model-facing correlation labels that are copied onto every event. */
12
+ export function isLedgerCorrelationIdentifier(value: unknown): value is string {
13
+ return typeof value === "string" && CORRELATION_IDENTIFIER.test(value);
14
+ }
15
+
16
+ /** A namespaced capability id with an ASCII, non-prose tail. Namespace wildcards remain representable. */
17
+ export function isLedgerCapabilityIdentifier(value: unknown): value is string {
18
+ if (typeof value !== "string") return false;
19
+ const prefix = CAPABILITY_PREFIXES.find((candidate) => value.startsWith(candidate));
20
+ return Boolean(prefix && CAPABILITY_TAIL.test(value.slice(prefix.length)));
21
+ }
@@ -18,11 +18,13 @@ import type { ApprovalSource } from "./approval.ts";
18
18
  import type { GrantRecord } from "./ledger.ts";
19
19
  import { isEscalationAttempt } from "./ledger.ts";
20
20
  import type { WorkspaceLeaseOutcome } from "./ledger-events.ts";
21
+ import { isExecutionId } from "./execution-id.ts";
22
+ import { validateLedgerV3Event } from "./ledger-v3-validation.ts";
21
23
 
22
24
  export interface LedgerReport {
23
25
  /** False when the file is absent — a configuration state, not damage. */
24
26
  exists: boolean;
25
- /** Capability-decision records (legacy or v2). */
27
+ /** Capability-decision records (legacy, v2, or v3). */
26
28
  records: number;
27
29
  /** Every parsed ledger event, including lifecycle and lease events. */
28
30
  events: number;
@@ -30,9 +32,11 @@ export interface LedgerReport {
30
32
  acquired: number; uncontended: number; refused: number; released: number;
31
33
  releasedUnrecorded: number; lost: number; retained: number; timeout: number; recovered: number;
32
34
  };
33
- lifecycle: { starting: number; completed: number; failed: number };
35
+ lifecycle: { starting: number; running: number; completed: number; failed: number };
36
+ /** Planned/observed/controller-validated facts; never counted as enforced child executions. */
37
+ workflowFacts: number;
34
38
  /** Lines that did not, with 1-based line numbers so the report is actionable. */
35
- corrupt: Array<{ line: number; text: string }>;
39
+ corrupt: Array<{ line: number; reason: string }>;
36
40
  /** Records where an agent asked for more than it held — ADR-0008's designated signal. */
37
41
  escalationAttempts: number;
38
42
  /**
@@ -139,18 +143,20 @@ const LEASE_OUTCOME_COUNTERS: Record<WorkspaceLeaseOutcome, keyof LedgerReport["
139
143
  recovered: "recovered",
140
144
  };
141
145
 
142
- function validV2Base(event: Record<string, unknown>): boolean {
143
- return event.ledgerVersion === 2 && typeof event.ts === "string";
144
- }
145
-
146
- function requireV2(event: Record<string, unknown>, fields: string[]): void {
147
- if (!validV2Base(event) || fields.some((field) => typeof event[field] !== "string" || event[field] === "")) {
146
+ function requireVersioned(event: Record<string, unknown>, fields: string[]): void {
147
+ if ((event.ledgerVersion !== 2 && event.ledgerVersion !== 3) || typeof event.ts !== "string" ||
148
+ fields.some((field) => typeof event[field] !== "string" || event[field] === "")) {
148
149
  throw new Error("invalid versioned ledger event");
149
150
  }
151
+ if (event.ledgerVersion === 3 &&
152
+ (!isExecutionId(event.executionId) ||
153
+ (event.parentExecutionId !== null && !isExecutionId(event.parentExecutionId)))) {
154
+ throw new Error("invalid v3 execution identity");
155
+ }
150
156
  }
151
157
 
152
158
  function requireCapabilityDecision(event: Record<string, unknown>): void {
153
- requireV2(event, ["parentId", "childId", "executor", "taskDigest"]);
159
+ requireVersioned(event, ["parentId", "childId", "executor", "taskDigest"]);
154
160
  for (const field of ["requested", "parentGrant", "effective", "denied", "clipped", "gatedBlocked"]) {
155
161
  if (!Array.isArray(event[field]) || !(event[field] as unknown[]).every((value) => typeof value === "string")) {
156
162
  throw new Error("invalid capability decision arrays");
@@ -172,7 +178,8 @@ export async function verifyLedger(path: string): Promise<LedgerReport> {
172
178
  records: 0,
173
179
  events: 0,
174
180
  workspaceLeases: { acquired: 0, uncontended: 0, refused: 0, released: 0, releasedUnrecorded: 0, lost: 0, retained: 0, timeout: 0, recovered: 0 },
175
- lifecycle: { starting: 0, completed: 0, failed: 0 },
181
+ lifecycle: { starting: 0, running: 0, completed: 0, failed: 0 },
182
+ workflowFacts: 0,
176
183
  corrupt: [],
177
184
  escalationAttempts: 0,
178
185
  executors: { herdr: 0, process: 0, unknown: 0 },
@@ -190,13 +197,14 @@ export async function verifyLedger(path: string): Promise<LedgerReport> {
190
197
  throw error;
191
198
  }
192
199
 
193
- const corrupt: Array<{ line: number; text: string }> = [];
200
+ const corrupt: Array<{ line: number; reason: string }> = [];
194
201
  // Keyed by name+digest: one name with two entries is the signal that the definition changed mid-ledger.
195
202
  const digests = new Map<string, { name: string; source: string; sha256: string; spawns: number }>();
196
203
  let records = 0;
197
204
  let events = 0;
198
205
  const workspaceLeases = { acquired: 0, uncontended: 0, refused: 0, released: 0, releasedUnrecorded: 0, lost: 0, retained: 0, timeout: 0, recovered: 0 };
199
- const lifecycle = { starting: 0, completed: 0, failed: 0 };
206
+ const lifecycle = { starting: 0, running: 0, completed: 0, failed: 0 };
207
+ let workflowFacts = 0;
200
208
  let escalationAttempts = 0;
201
209
  const executors = { herdr: 0, process: 0, unknown: 0 };
202
210
  const bySource: Record<ApprovalSource, number> = { prompt: 0, session: 0, persisted: 0, inherited: 0 };
@@ -217,11 +225,21 @@ export async function verifyLedger(path: string): Promise<LedgerReport> {
217
225
  if (raw.trim().length === 0) return;
218
226
  try {
219
227
  const event = JSON.parse(raw) as Record<string, unknown>;
220
- if (event.ledgerVersion !== undefined && event.event === undefined) {
221
- throw new Error("versioned ledger line has no event discriminator");
228
+ if (event.ledgerVersion !== undefined) {
229
+ if (event.event === undefined) throw new Error("versioned ledger line has no event discriminator");
230
+ if (event.ledgerVersion !== 2 && event.ledgerVersion !== 3) throw new Error("unsupported ledger version");
231
+ if (event.ledgerVersion === 3 && validateLedgerV3Event(event)) throw new Error("invalid ledger v3 event");
232
+ }
233
+ if (event.event === "workflow_fact") {
234
+ // v2 has no workflow facts; the exact v3 validator above already checked its vocabulary,
235
+ // correlation and provenance/state relation.
236
+ if (event.ledgerVersion !== 3) throw new Error("invalid workflow fact event");
237
+ workflowFacts += 1;
238
+ events += 1;
239
+ return;
222
240
  }
223
241
  if (event.event === "workspace_lease") {
224
- requireV2(event, ["childId", "workspaceId", "root", "access"]);
242
+ requireVersioned(event, ["childId", "workspaceId", "root", "access"]);
225
243
  const outcome = typeof event.outcome === "string" ? event.outcome : "";
226
244
  // Explicit map, not a name match: the ledger's outcome vocabulary and these counter names are
227
245
  // allowed to differ, and a valid event must never be counted as corruption because a counter
@@ -235,7 +253,7 @@ export async function verifyLedger(path: string): Promise<LedgerReport> {
235
253
  return;
236
254
  }
237
255
  if (event.event === "child_lifecycle") {
238
- requireV2(event, ["childId", "executor"]);
256
+ requireVersioned(event, ["childId", "executor"]);
239
257
  const state = typeof event.state === "string" ? event.state : "";
240
258
  if (!Object.hasOwn(lifecycle, state)) throw new Error("invalid child lifecycle event");
241
259
  lifecycle[state as keyof typeof lifecycle] += 1;
@@ -243,7 +261,7 @@ export async function verifyLedger(path: string): Promise<LedgerReport> {
243
261
  return;
244
262
  }
245
263
  if (event.event === "check_receipt") {
246
- requireV2(event, ["childId", "receiptId", "workspaceId", "checkId", "treeSha"]);
264
+ requireVersioned(event, ["childId", "receiptId", "workspaceId", "checkId", "treeSha"]);
247
265
  events += 1;
248
266
  return;
249
267
  }
@@ -305,7 +323,9 @@ export async function verifyLedger(path: string): Promise<LedgerReport> {
305
323
  else digests.set(key, { name: d.name, source: d.source, sha256: d.sha256, spawns: 1 });
306
324
  }
307
325
  } catch {
308
- corrupt.push({ line: index + 1, text: raw.slice(0, 120) });
326
+ // Content-free by contract: a torn or foreign line can contain exactly the task/output material the
327
+ // ledger and dashboard promise never to expose. The line number locates the evidence without copying it.
328
+ corrupt.push({ line: index + 1, reason: "invalid ledger line" });
309
329
  }
310
330
  });
311
331
 
@@ -315,6 +335,7 @@ export async function verifyLedger(path: string): Promise<LedgerReport> {
315
335
  events,
316
336
  workspaceLeases,
317
337
  lifecycle,
338
+ workflowFacts,
318
339
  corrupt,
319
340
  escalationAttempts,
320
341
  executors,