pi-daddy 0.19.0 → 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.
- package/CHANGELOG.md +65 -0
- package/README.md +105 -36
- package/contracts/ledger/v3/README.md +36 -0
- package/contracts/ledger/v3/fixtures/capability-decision.json +98 -0
- package/contracts/ledger/v3/fixtures/check-receipt.json +42 -0
- package/contracts/ledger/v3/fixtures/child-lifecycle.json +45 -0
- package/contracts/ledger/v3/fixtures/workflow-fact.json +41 -0
- package/contracts/ledger/v3/fixtures/workspace-lease.json +43 -0
- package/contracts/ledger/v3/ledger-event.schema.json +930 -0
- package/dist/check-runner.d.ts.map +1 -1
- package/dist/check-runner.js +14 -0
- package/dist/check-runner.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +10 -0
- package/dist/correlation.js.map +1 -1
- package/dist/dashboard-cli.d.ts +18 -0
- package/dist/dashboard-cli.d.ts.map +1 -0
- package/dist/dashboard-cli.js +154 -0
- package/dist/dashboard-cli.js.map +1 -0
- package/dist/dashboard-handshake.d.ts +37 -0
- package/dist/dashboard-handshake.d.ts.map +1 -0
- package/dist/dashboard-handshake.js +127 -0
- package/dist/dashboard-handshake.js.map +1 -0
- package/dist/dashboard-herdr.d.ts +54 -0
- package/dist/dashboard-herdr.d.ts.map +1 -0
- package/dist/dashboard-herdr.js +286 -0
- package/dist/dashboard-herdr.js.map +1 -0
- package/dist/dashboard-projection.d.ts +73 -0
- package/dist/dashboard-projection.d.ts.map +1 -0
- package/dist/dashboard-projection.js +294 -0
- package/dist/dashboard-projection.js.map +1 -0
- package/dist/dashboard-render.d.ts +10 -0
- package/dist/dashboard-render.d.ts.map +1 -0
- package/dist/dashboard-render.js +208 -0
- package/dist/dashboard-render.js.map +1 -0
- package/dist/delegate-types.d.ts +6 -2
- package/dist/delegate-types.d.ts.map +1 -1
- package/dist/delegate-types.js.map +1 -1
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +4 -1
- package/dist/delegate.js.map +1 -1
- package/dist/execution-id.d.ts +6 -0
- package/dist/execution-id.d.ts.map +1 -0
- package/dist/execution-id.js +13 -0
- package/dist/execution-id.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger-events.d.ts +27 -4
- package/dist/ledger-events.d.ts.map +1 -1
- package/dist/ledger-events.js +30 -9
- package/dist/ledger-events.js.map +1 -1
- package/dist/ledger-identifiers.d.ts +7 -0
- package/dist/ledger-identifiers.d.ts.map +1 -0
- package/dist/ledger-identifiers.js +20 -0
- package/dist/ledger-identifiers.js.map +1 -0
- package/dist/ledger-report.d.ts +5 -2
- package/dist/ledger-report.d.ts.map +1 -1
- package/dist/ledger-report.js +38 -14
- package/dist/ledger-report.js.map +1 -1
- package/dist/ledger-v3-validation.d.ts +11 -0
- package/dist/ledger-v3-validation.d.ts.map +1 -0
- package/dist/ledger-v3-validation.js +265 -0
- package/dist/ledger-v3-validation.js.map +1 -0
- package/dist/ledger.d.ts +18 -5
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +29 -7
- package/dist/ledger.js.map +1 -1
- package/dist/propagation.d.ts +3 -1
- package/dist/propagation.d.ts.map +1 -1
- package/dist/propagation.js +3 -0
- package/dist/propagation.js.map +1 -1
- package/dist/run-child.d.ts +3 -1
- package/dist/run-child.d.ts.map +1 -1
- package/dist/run-child.js +30 -2
- package/dist/run-child.js.map +1 -1
- package/dist/run-herdr.d.ts +2 -0
- package/dist/run-herdr.d.ts.map +1 -1
- package/dist/run-herdr.js +8 -0
- package/dist/run-herdr.js.map +1 -1
- package/dist/workflow-fact-id.d.ts +4 -0
- package/dist/workflow-fact-id.d.ts.map +1 -0
- package/dist/workflow-fact-id.js +14 -0
- package/dist/workflow-fact-id.js.map +1 -0
- package/dist/workflow-facts.d.ts +34 -0
- package/dist/workflow-facts.d.ts.map +1 -0
- package/dist/workflow-facts.js +43 -0
- package/dist/workflow-facts.js.map +1 -0
- package/extensions/chain-ledger.ts +4 -0
- package/extensions/chain-plan.ts +98 -0
- package/extensions/delegate-chain.ts +63 -122
- package/extensions/delegation-ledger.ts +60 -0
- package/extensions/delegation.ts +13 -13
- package/extensions/execute-child.ts +85 -21
- package/extensions/execution-occurrence.ts +20 -0
- package/extensions/grants-command.ts +22 -3
- package/extensions/grants.ts +44 -0
- package/extensions/run-delegation.ts +36 -56
- package/extensions/session.ts +7 -2
- package/extensions/workspace-runtime.ts +10 -0
- package/herdr-plugin/herdr-plugin.toml +18 -0
- package/package.json +16 -4
- package/src/check-runner.ts +16 -0
- package/src/correlation.ts +11 -0
- package/src/dashboard-cli.ts +171 -0
- package/src/dashboard-handshake.ts +185 -0
- package/src/dashboard-herdr.ts +355 -0
- package/src/dashboard-projection.ts +390 -0
- package/src/dashboard-render.ts +254 -0
- package/src/delegate-types.ts +6 -2
- package/src/delegate.ts +4 -2
- package/src/execution-id.ts +18 -0
- package/src/index.ts +25 -0
- package/src/ledger-events.ts +54 -10
- package/src/ledger-identifiers.ts +21 -0
- package/src/ledger-report.ts +40 -19
- package/src/ledger-v3-validation.ts +266 -0
- package/src/ledger.ts +49 -10
- package/src/propagation.ts +3 -0
- package/src/run-child.ts +35 -3
- package/src/run-herdr.ts +9 -0
- package/src/workflow-fact-id.ts +16 -0
- package/src/workflow-facts.ts +65 -0
package/src/delegate.ts
CHANGED
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
// charged to every caller for a line count they did not cause.
|
|
20
20
|
export { DELEGATE_CAPABILITY, agentCapability, maySpawnDefinition, normaliseCapability } from "./capabilities.ts";
|
|
21
21
|
import {
|
|
22
|
-
ENV_APPROVED, ENV_DEPTH, ENV_FANOUT, ENV_GATED, ENV_GRANT, ENV_LEDGER, ENV_MAX_DEPTH,
|
|
23
|
-
inheritableGrant,
|
|
22
|
+
ENV_APPROVED, ENV_DEPTH, ENV_EXECUTION_ID, ENV_FANOUT, ENV_GATED, ENV_GRANT, ENV_LEDGER, ENV_MAX_DEPTH,
|
|
23
|
+
ENV_PARENT_ID, inheritableGrant,
|
|
24
24
|
} from "./propagation.ts";
|
|
25
25
|
import { inheritApprovals, type InheritableApproval } from "./approval.ts";
|
|
26
26
|
import { suggestForUnknown, unknownCapabilities, type Catalog } from "./catalog.ts";
|
|
@@ -309,6 +309,7 @@ export function planDelegation(request: DelegationRequest, ctx: DelegationContex
|
|
|
309
309
|
// process boundaries with no shared state.
|
|
310
310
|
if (ctx.fanoutBudget !== undefined) env[ENV_FANOUT] = String(ctx.fanoutBudget);
|
|
311
311
|
if (ctx.childSpawnId) env[ENV_PARENT_ID] = ctx.childSpawnId;
|
|
312
|
+
if (ctx.childExecutionId) env[ENV_EXECUTION_ID] = ctx.childExecutionId;
|
|
312
313
|
if (ctx.gated.length > 0) env[ENV_GATED] = ctx.gated.join(",");
|
|
313
314
|
// Approvals ride down with the grant, but only ever for what this child actually received — so
|
|
314
315
|
// `approved ⊆ grant` holds at every level (ADR-0010). Written even when empty, so this object states
|
|
@@ -330,6 +331,7 @@ export function planDelegation(request: DelegationRequest, ctx: DelegationContex
|
|
|
330
331
|
childDepth,
|
|
331
332
|
requested,
|
|
332
333
|
childId: ctx.childSpawnId,
|
|
334
|
+
executionId: ctx.childExecutionId,
|
|
333
335
|
taskDigest,
|
|
334
336
|
...(correlation ? { correlation } : {}),
|
|
335
337
|
...(approvalBinding ? { approvalBinding } : {}),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/** Unique identity for one governed execution occurrence. Logical child ids remain readable positions. */
|
|
4
|
+
export type ExecutionId = `exec:${string}`;
|
|
5
|
+
|
|
6
|
+
const EXECUTION_ID_RE = /^exec:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
|
+
|
|
8
|
+
export function isExecutionId(value: unknown): value is ExecutionId {
|
|
9
|
+
return typeof value === "string" && EXECUTION_ID_RE.test(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function newExecutionId(): ExecutionId {
|
|
13
|
+
return `exec:${randomUUID()}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function assertExecutionId(value: unknown, field = "executionId"): asserts value is ExecutionId {
|
|
17
|
+
if (!isExecutionId(value)) throw new TypeError(`${field} must be a pi-daddy execution id`);
|
|
18
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,10 @@ export {
|
|
|
15
15
|
buildChildLifecycleEvent,
|
|
16
16
|
buildRecord,
|
|
17
17
|
buildWorkspaceLeaseEvent,
|
|
18
|
+
buildWorkflowFactEvent,
|
|
19
|
+
WORKFLOW_FACT_KINDS,
|
|
20
|
+
WORKFLOW_FACT_PROVENANCE,
|
|
21
|
+
WORKFLOW_FACT_STATES,
|
|
18
22
|
isEscalationAttempt,
|
|
19
23
|
LEDGER_VERSION,
|
|
20
24
|
type CheckReceiptLedgerEvent,
|
|
@@ -24,6 +28,10 @@ export {
|
|
|
24
28
|
type RuntimeLedgerEvent,
|
|
25
29
|
type WorkspaceLeaseEvent,
|
|
26
30
|
type WorkspaceLeaseOutcome,
|
|
31
|
+
type WorkflowFactEvent,
|
|
32
|
+
type WorkflowFactKind,
|
|
33
|
+
type WorkflowFactProvenance,
|
|
34
|
+
type WorkflowFactState,
|
|
27
35
|
} from "./ledger.ts";
|
|
28
36
|
|
|
29
37
|
export { planSpawn, type SpawnPlan, type SpawnPlanInput } from "./spawn.ts";
|
|
@@ -97,6 +105,23 @@ export {
|
|
|
97
105
|
type CheckRegistry,
|
|
98
106
|
} from "./check-runner.ts";
|
|
99
107
|
|
|
108
|
+
export {
|
|
109
|
+
isExecutionId,
|
|
110
|
+
newExecutionId,
|
|
111
|
+
type ExecutionId,
|
|
112
|
+
} from "./execution-id.ts";
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
parseDashboardLedger,
|
|
116
|
+
type DashboardNode,
|
|
117
|
+
type DashboardProjection,
|
|
118
|
+
type DashboardState,
|
|
119
|
+
type DashboardWorkflow,
|
|
120
|
+
type DashboardWorkflowFact,
|
|
121
|
+
} from "./dashboard-projection.ts";
|
|
122
|
+
|
|
123
|
+
export { renderDashboard, type DashboardRenderOptions } from "./dashboard-render.ts";
|
|
124
|
+
|
|
100
125
|
export {
|
|
101
126
|
createApprovalGate,
|
|
102
127
|
createApprovalGateProvider,
|
package/src/ledger-events.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
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,9 @@ 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";
|
|
11
14
|
|
|
12
15
|
export const WORKSPACE_ACCESSES = ["read", "write"] as const;
|
|
13
16
|
export type WorkspaceAccess = typeof WORKSPACE_ACCESSES[number];
|
|
@@ -15,10 +18,10 @@ export type WorkspaceAccess = typeof WORKSPACE_ACCESSES[number];
|
|
|
15
18
|
export const WORKSPACE_RECOVERY_VALUES = [false, true, "unknown"] as const;
|
|
16
19
|
export type WorkspaceRecovery = typeof WORKSPACE_RECOVERY_VALUES[number];
|
|
17
20
|
|
|
18
|
-
export const CHILD_LIFECYCLE_STATES = ["starting", "completed", "failed"] as const;
|
|
21
|
+
export const CHILD_LIFECYCLE_STATES = ["starting", "running", "completed", "failed"] as const;
|
|
19
22
|
export type ChildLifecycleState = typeof CHILD_LIFECYCLE_STATES[number];
|
|
20
23
|
|
|
21
|
-
/** The complete Node signal vocabulary accepted by a
|
|
24
|
+
/** The complete Node signal vocabulary accepted by a v3 child-lifecycle event. */
|
|
22
25
|
export const CHILD_PROCESS_SIGNALS = [
|
|
23
26
|
"SIGABRT", "SIGALRM", "SIGBUS", "SIGCHLD", "SIGCONT", "SIGFPE", "SIGHUP", "SIGILL", "SIGINT", "SIGIO",
|
|
24
27
|
"SIGIOT", "SIGKILL", "SIGPIPE", "SIGPOLL", "SIGPROF", "SIGPWR", "SIGQUIT", "SIGSEGV", "SIGSTKFLT",
|
|
@@ -47,6 +50,8 @@ export type WorkspaceLeaseOutcome = typeof WORKSPACE_LEASE_OUTCOMES[number];
|
|
|
47
50
|
export interface WorkspaceLeaseEvent extends LedgerEventBase {
|
|
48
51
|
ledgerVersion: typeof LEDGER_VERSION;
|
|
49
52
|
event: "workspace_lease";
|
|
53
|
+
executionId: string;
|
|
54
|
+
parentExecutionId: string | null;
|
|
50
55
|
childId: string;
|
|
51
56
|
workspaceId: string;
|
|
52
57
|
root: string;
|
|
@@ -61,9 +66,16 @@ export interface WorkspaceLeaseEvent extends LedgerEventBase {
|
|
|
61
66
|
export interface ChildLifecycleEvent extends LedgerEventBase {
|
|
62
67
|
ledgerVersion: typeof LEDGER_VERSION;
|
|
63
68
|
event: "child_lifecycle";
|
|
69
|
+
executionId: string;
|
|
70
|
+
parentExecutionId: string | null;
|
|
64
71
|
childId: string;
|
|
65
72
|
state: ChildLifecycleState;
|
|
66
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;
|
|
67
79
|
exitCode?: number | null;
|
|
68
80
|
signal?: ChildProcessSignal | null;
|
|
69
81
|
timedOut?: true;
|
|
@@ -75,6 +87,8 @@ export interface ChildLifecycleEvent extends LedgerEventBase {
|
|
|
75
87
|
export interface CheckReceiptLedgerEvent extends LedgerEventBase {
|
|
76
88
|
ledgerVersion: typeof LEDGER_VERSION;
|
|
77
89
|
event: "check_receipt";
|
|
90
|
+
executionId: string;
|
|
91
|
+
parentExecutionId: string | null;
|
|
78
92
|
childId: string;
|
|
79
93
|
receiptId: string;
|
|
80
94
|
workspaceId: string;
|
|
@@ -85,6 +99,8 @@ export interface CheckReceiptLedgerEvent extends LedgerEventBase {
|
|
|
85
99
|
export type CapabilityDecisionEvent = GrantRecord & {
|
|
86
100
|
ledgerVersion: typeof LEDGER_VERSION;
|
|
87
101
|
event: "capability_decision";
|
|
102
|
+
executionId: string;
|
|
103
|
+
parentExecutionId: string | null;
|
|
88
104
|
taskDigest: string;
|
|
89
105
|
};
|
|
90
106
|
|
|
@@ -92,9 +108,12 @@ export type RuntimeLedgerEvent =
|
|
|
92
108
|
| CapabilityDecisionEvent
|
|
93
109
|
| WorkspaceLeaseEvent
|
|
94
110
|
| ChildLifecycleEvent
|
|
95
|
-
| CheckReceiptLedgerEvent
|
|
111
|
+
| CheckReceiptLedgerEvent
|
|
112
|
+
| WorkflowFactEvent;
|
|
96
113
|
|
|
97
114
|
export function buildWorkspaceLeaseEvent(args: {
|
|
115
|
+
executionId: string;
|
|
116
|
+
parentExecutionId: string | null;
|
|
98
117
|
childId: string;
|
|
99
118
|
workspaceId: string;
|
|
100
119
|
root: string;
|
|
@@ -106,10 +125,13 @@ export function buildWorkspaceLeaseEvent(args: {
|
|
|
106
125
|
correlation?: CorrelationMetadata;
|
|
107
126
|
now: Date;
|
|
108
127
|
}): WorkspaceLeaseEvent {
|
|
109
|
-
|
|
128
|
+
assertEventIdentity(args);
|
|
129
|
+
return assertLedgerV3Wire({
|
|
110
130
|
ledgerVersion: LEDGER_VERSION,
|
|
111
131
|
event: "workspace_lease",
|
|
112
132
|
ts: args.now.toISOString(),
|
|
133
|
+
executionId: args.executionId,
|
|
134
|
+
parentExecutionId: args.parentExecutionId,
|
|
113
135
|
childId: args.childId,
|
|
114
136
|
workspaceId: args.workspaceId,
|
|
115
137
|
root: args.root,
|
|
@@ -123,10 +145,12 @@ export function buildWorkspaceLeaseEvent(args: {
|
|
|
123
145
|
...(args.releaseReason ? { releaseReason: args.releaseReason } : {}),
|
|
124
146
|
...(args.refusal ? { refusal: structuredClone(args.refusal) } : {}),
|
|
125
147
|
...(args.correlation ? { correlation: structuredClone(args.correlation) } : {}),
|
|
126
|
-
};
|
|
148
|
+
});
|
|
127
149
|
}
|
|
128
150
|
|
|
129
151
|
export function buildCheckReceiptLedgerEvent(args: {
|
|
152
|
+
executionId: string;
|
|
153
|
+
parentExecutionId: string | null;
|
|
130
154
|
childId: string;
|
|
131
155
|
receiptId: string;
|
|
132
156
|
workspaceId: string;
|
|
@@ -135,26 +159,34 @@ export function buildCheckReceiptLedgerEvent(args: {
|
|
|
135
159
|
correlation?: CorrelationMetadata;
|
|
136
160
|
now: Date;
|
|
137
161
|
}): CheckReceiptLedgerEvent {
|
|
162
|
+
assertEventIdentity(args);
|
|
138
163
|
if (!/^[a-f0-9]{64}$/i.test(args.receiptId)) {
|
|
139
164
|
throw new TypeError("receiptId must be a SHA-256 hex digest");
|
|
140
165
|
}
|
|
141
|
-
return {
|
|
166
|
+
return assertLedgerV3Wire({
|
|
142
167
|
ledgerVersion: LEDGER_VERSION,
|
|
143
168
|
event: "check_receipt",
|
|
144
169
|
ts: args.now.toISOString(),
|
|
170
|
+
executionId: args.executionId,
|
|
171
|
+
parentExecutionId: args.parentExecutionId,
|
|
145
172
|
childId: args.childId,
|
|
146
173
|
receiptId: args.receiptId,
|
|
147
174
|
workspaceId: args.workspaceId,
|
|
148
175
|
checkId: args.checkId,
|
|
149
176
|
treeSha: args.treeSha,
|
|
150
177
|
...(args.correlation ? { correlation: structuredClone(args.correlation) } : {}),
|
|
151
|
-
};
|
|
178
|
+
});
|
|
152
179
|
}
|
|
153
180
|
|
|
154
181
|
export function buildChildLifecycleEvent(args: {
|
|
182
|
+
executionId: string;
|
|
183
|
+
parentExecutionId: string | null;
|
|
155
184
|
childId: string;
|
|
156
185
|
state: ChildLifecycleState;
|
|
157
186
|
executor: ExecutorKind;
|
|
187
|
+
deadlineAt?: string;
|
|
188
|
+
herdrPaneId?: string;
|
|
189
|
+
herdrAgentName?: string;
|
|
158
190
|
exitCode?: number | null;
|
|
159
191
|
signal?: ChildProcessSignal | null;
|
|
160
192
|
timedOut?: boolean;
|
|
@@ -164,13 +196,19 @@ export function buildChildLifecycleEvent(args: {
|
|
|
164
196
|
correlation?: CorrelationMetadata;
|
|
165
197
|
now: Date;
|
|
166
198
|
}): ChildLifecycleEvent {
|
|
167
|
-
|
|
199
|
+
assertEventIdentity(args);
|
|
200
|
+
return assertLedgerV3Wire({
|
|
168
201
|
ledgerVersion: LEDGER_VERSION,
|
|
169
202
|
event: "child_lifecycle",
|
|
170
203
|
ts: args.now.toISOString(),
|
|
204
|
+
executionId: args.executionId,
|
|
205
|
+
parentExecutionId: args.parentExecutionId,
|
|
171
206
|
childId: args.childId,
|
|
172
207
|
state: args.state,
|
|
173
208
|
executor: args.executor,
|
|
209
|
+
...(args.deadlineAt ? { deadlineAt: args.deadlineAt } : {}),
|
|
210
|
+
...(args.herdrPaneId ? { herdrPaneId: args.herdrPaneId } : {}),
|
|
211
|
+
...(args.herdrAgentName ? { herdrAgentName: args.herdrAgentName } : {}),
|
|
174
212
|
...(args.exitCode !== undefined ? { exitCode: args.exitCode } : {}),
|
|
175
213
|
...(args.signal !== undefined ? { signal: args.signal } : {}),
|
|
176
214
|
...(args.timedOut ? { timedOut: true } : {}),
|
|
@@ -178,5 +216,11 @@ export function buildChildLifecycleEvent(args: {
|
|
|
178
216
|
...(args.truncated ? { truncated: true } : {}),
|
|
179
217
|
...(args.reason ? { reason: args.reason } : {}),
|
|
180
218
|
...(args.correlation ? { correlation: structuredClone(args.correlation) } : {}),
|
|
181
|
-
};
|
|
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");
|
|
182
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
|
+
}
|
package/src/ledger-report.ts
CHANGED
|
@@ -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
|
|
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;
|
|
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
|
|
143
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|