osuite 2.8.0 → 2.9.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/LICENSE +1 -1
- package/README.md +57 -3
- package/cli.js +13 -10
- package/dashclaw.js +364 -4
- package/index.cjs +109 -0
- package/osuite.js +10 -1
- package/package.json +2 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# OSuite SDK (v2.
|
|
1
|
+
# OSuite SDK (v2.9.0)
|
|
2
2
|
|
|
3
3
|
**Minimal governance runtime for AI agents.**
|
|
4
4
|
|
|
@@ -70,9 +70,9 @@ claw.update_outcome(action_id, status="completed")
|
|
|
70
70
|
|
|
71
71
|
---
|
|
72
72
|
|
|
73
|
-
## SDK Surface Area
|
|
73
|
+
## SDK Surface Area
|
|
74
74
|
|
|
75
|
-
The v2 SDK exposes
|
|
75
|
+
The v2 SDK exposes the core governance loop plus first-class PCAA, replay, proof, and evidence helpers:
|
|
76
76
|
|
|
77
77
|
### Core Runtime
|
|
78
78
|
- `guard(context)` -- Policy evaluation ("Can I do X?"). Returns `risk_score` (server-computed) and `agent_risk_score` (raw agent value)
|
|
@@ -88,6 +88,60 @@ The v2 SDK exposes **45 methods** optimized for stability and zero-overhead gove
|
|
|
88
88
|
- `resolveOpenLoop(loopId, status, res)` -- Resolve pending loops.
|
|
89
89
|
- `getSignals()` -- Get current risk signals across all agents.
|
|
90
90
|
|
|
91
|
+
### Proof And Attestation
|
|
92
|
+
- `GET /api/governance/proof` -- Return the operator-facing governance proof snapshot.
|
|
93
|
+
- `GET /api/governance/proof?format=bundle` -- Return the machine-readable organization proof bundle (`dc.proof.v1`).
|
|
94
|
+
- `GET /api/governance/proof?action_id=<id>&format=bundle` -- Return the machine-readable action proof bundle (`dc.proof.v1`).
|
|
95
|
+
- `POST /api/governance/proof/verify` -- Verify one action attestation against the canonical action digest and stored wallet material.
|
|
96
|
+
- `getActionTrace(actionId)` -- Fetch replay traces, assumptions, loops, and root-cause indicators for one action.
|
|
97
|
+
- `getGovernanceProof({ actionId, format })` -- Read proof snapshots through the SDK instead of hand-building URLs.
|
|
98
|
+
- `getGovernanceProofBundle({ actionId })` -- Fetch machine-readable PCAA / proof bundles for an org or one action.
|
|
99
|
+
- `verifyGovernanceProof(actionId)` -- Verify attestation/proof integrity and return structured checks.
|
|
100
|
+
- `getComplianceEvidence({ window })` -- Pull live evidence aggregates for compliance and PCAA health.
|
|
101
|
+
- `exportProof({ actionId, bundleId })` -- Export the portable verifier-facing proof package.
|
|
102
|
+
- `getPcaaHealth({ window })` -- Compute checkpoint coverage, approval trigger rate, and evidence completion.
|
|
103
|
+
- `getPcaaAction(actionId)` -- Fetch one action's replay/proof/export bundle and derive checkpoint states.
|
|
104
|
+
- `events()` -- Subscribe to live approval and governance events over SSE.
|
|
105
|
+
- `PCAA_CHECKPOINTS` / `buildPcaaCheckpointStates(...)` -- Reuse the portable checkpoint contract in your own tooling.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
curl -H "x-api-key: $DASHCLAW_API_KEY" \
|
|
111
|
+
"$DASHCLAW_BASE_URL/api/governance/proof?format=bundle"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Bundle verification example:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
curl -X POST \
|
|
118
|
+
-H "content-type: application/json" \
|
|
119
|
+
-H "x-api-key: $DASHCLAW_API_KEY" \
|
|
120
|
+
-d '{"action_id":"act_123"}' \
|
|
121
|
+
"$DASHCLAW_BASE_URL/api/governance/proof/verify"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
PCAA health example:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const health = await claw.getPcaaHealth({ window: '30d' });
|
|
128
|
+
console.log(health.checkpointCoverage); // 0-100
|
|
129
|
+
console.log(health.approvalRate); // 0-100
|
|
130
|
+
console.log(health.evidenceCompletion); // 0-100
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
PCAA replay example:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
const pcaa = await claw.getPcaaAction('act_123');
|
|
137
|
+
console.log(pcaa.checkpoints);
|
|
138
|
+
// [
|
|
139
|
+
// { id: 'pre_action_admissibility', status: 'complete', value: 'require_approval' },
|
|
140
|
+
// { id: 'action_open', status: 'complete', value: 'act_123' },
|
|
141
|
+
// ...
|
|
142
|
+
// ]
|
|
143
|
+
```
|
|
144
|
+
|
|
91
145
|
### Swarm & Connectivity
|
|
92
146
|
- `heartbeat(status, metadata)` -- Report agent presence and health.
|
|
93
147
|
- `reportConnections(connections)` -- Report active provider connections.
|
package/cli.js
CHANGED
|
@@ -14,12 +14,11 @@ Usage:
|
|
|
14
14
|
osuite deny <actionId> [--reason "Outside change window"]
|
|
15
15
|
|
|
16
16
|
Environment:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
The legacy "dashclaw" binary name is still available as an alias in this package.
|
|
17
|
+
OSUITE_BASE_URL Required, your OSuite base URL
|
|
18
|
+
OSUITE_URL Optional fallback for hosted/base URL
|
|
19
|
+
NEXTAUTH_URL Optional fallback when reusing app env files
|
|
20
|
+
OSUITE_API_KEY Required, admin API key for approval operations
|
|
21
|
+
OSUITE_AGENT_ID Optional, defaults to "osuite-cli"
|
|
23
22
|
`);
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -47,12 +46,16 @@ function parseArgs(argv) {
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
function getClient() {
|
|
50
|
-
const baseUrl = process.env.
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const baseUrl = process.env.OSUITE_BASE_URL
|
|
50
|
+
|| process.env.DASHCLAW_BASE_URL
|
|
51
|
+
|| process.env.OSUITE_URL
|
|
52
|
+
|| process.env.DASHCLAW_URL
|
|
53
|
+
|| process.env.NEXTAUTH_URL;
|
|
54
|
+
const apiKey = process.env.OSUITE_API_KEY || process.env.DASHCLAW_API_KEY;
|
|
55
|
+
const agentId = process.env.OSUITE_AGENT_ID || process.env.DASHCLAW_AGENT_ID || 'osuite-cli';
|
|
53
56
|
|
|
54
57
|
if (!baseUrl || !apiKey) {
|
|
55
|
-
process.stderr.write('Missing
|
|
58
|
+
process.stderr.write('Missing a base URL env (OSUITE_BASE_URL / OSUITE_URL / NEXTAUTH_URL) or OSUITE_API_KEY.\n');
|
|
56
59
|
process.exit(1);
|
|
57
60
|
}
|
|
58
61
|
|
package/dashclaw.js
CHANGED
|
@@ -19,6 +19,118 @@ class GuardBlockedError extends Error {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const PCAA_CHECKPOINTS = [
|
|
23
|
+
{
|
|
24
|
+
id: 'pre_action_admissibility',
|
|
25
|
+
title: 'Pre-action admissibility',
|
|
26
|
+
summary: 'Evaluate whether the proposed action is allowed, simulated first, blocked, or approval-gated before side effects happen.',
|
|
27
|
+
sdkMethod: 'guard',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'action_open',
|
|
31
|
+
title: 'Action open',
|
|
32
|
+
summary: 'Create the portable action record that becomes the trust object for replay, scoring, and proof.',
|
|
33
|
+
sdkMethod: 'createAction',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'assumption_capture',
|
|
37
|
+
title: 'Assumption capture',
|
|
38
|
+
summary: 'Record what the runtime believed or depended on so operators can replay the reasoning boundary later.',
|
|
39
|
+
sdkMethod: 'recordAssumption',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'approval_checkpoint',
|
|
43
|
+
title: 'Approval checkpoint',
|
|
44
|
+
summary: 'Pause, wait, or externally hold execution when policy requires a human checkpoint.',
|
|
45
|
+
sdkMethod: 'waitForApproval',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'outcome_closure',
|
|
49
|
+
title: 'Outcome closure',
|
|
50
|
+
summary: 'Write the final result, evidence, and status so the action certificate closes cleanly.',
|
|
51
|
+
sdkMethod: 'updateOutcome',
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function toPercent(numerator, denominator) {
|
|
56
|
+
if (!denominator || denominator <= 0) return 0;
|
|
57
|
+
return Math.max(0, Math.min(100, Math.round((numerator / denominator) * 100)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveRouteDecision({ action = null, proofBundle = null } = {}) {
|
|
61
|
+
return (
|
|
62
|
+
proofBundle?.route_decision
|
|
63
|
+
|| proofBundle?.action_certificate?.route_decision
|
|
64
|
+
|| action?.policy_snapshot?.effective_decision
|
|
65
|
+
|| null
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveOutcomeStatus(status) {
|
|
70
|
+
const normalized = String(status || '').toLowerCase();
|
|
71
|
+
if (['completed', 'failed', 'blocked', 'cancelled', 'denied'].includes(normalized)) return 'complete';
|
|
72
|
+
if (['running', 'pending', 'pending_approval'].includes(normalized)) return 'pending';
|
|
73
|
+
return 'inactive';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildPcaaCheckpointStates({ action = null, trace = null, proofBundle = null } = {}) {
|
|
77
|
+
const routeDecision = resolveRouteDecision({ action, proofBundle });
|
|
78
|
+
const assumptionCount = Number(
|
|
79
|
+
trace?.trace?.assumptions?.total
|
|
80
|
+
?? trace?.assumptions?.total
|
|
81
|
+
?? (Array.isArray(trace?.assumptions) ? trace.assumptions.length : 0),
|
|
82
|
+
);
|
|
83
|
+
const approvalTriggered = ['require_approval', 'require_dual_approval', 'simulate_first', 'warn', 'block']
|
|
84
|
+
.includes(String(routeDecision || '').toLowerCase())
|
|
85
|
+
|| String(action?.status || '').toLowerCase() === 'pending_approval';
|
|
86
|
+
const approvalComplete = Boolean(
|
|
87
|
+
action?.approved_by
|
|
88
|
+
|| action?.approved_at
|
|
89
|
+
|| (Array.isArray(proofBundle?.approvals) && proofBundle.approvals.length > 0),
|
|
90
|
+
);
|
|
91
|
+
const outcomeStatus = resolveOutcomeStatus(action?.status);
|
|
92
|
+
|
|
93
|
+
return PCAA_CHECKPOINTS.map((checkpoint) => {
|
|
94
|
+
if (checkpoint.id === 'pre_action_admissibility') {
|
|
95
|
+
return {
|
|
96
|
+
...checkpoint,
|
|
97
|
+
status: routeDecision ? 'complete' : 'inactive',
|
|
98
|
+
value: routeDecision || 'not evaluated',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (checkpoint.id === 'action_open') {
|
|
103
|
+
return {
|
|
104
|
+
...checkpoint,
|
|
105
|
+
status: action?.action_id ? 'complete' : 'inactive',
|
|
106
|
+
value: action?.action_id || '--',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (checkpoint.id === 'assumption_capture') {
|
|
111
|
+
return {
|
|
112
|
+
...checkpoint,
|
|
113
|
+
status: assumptionCount > 0 ? 'complete' : 'inactive',
|
|
114
|
+
value: assumptionCount > 0 ? `${assumptionCount} recorded` : 'none recorded',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (checkpoint.id === 'approval_checkpoint') {
|
|
119
|
+
return {
|
|
120
|
+
...checkpoint,
|
|
121
|
+
status: approvalComplete ? 'complete' : approvalTriggered ? 'active' : 'inactive',
|
|
122
|
+
value: approvalComplete ? 'approved' : approvalTriggered ? (routeDecision || 'active') : 'not required',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...checkpoint,
|
|
128
|
+
status: outcomeStatus,
|
|
129
|
+
value: action?.status || '--',
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
22
134
|
class DashClaw {
|
|
23
135
|
/**
|
|
24
136
|
* @param {Object} options
|
|
@@ -60,7 +172,7 @@ class DashClaw {
|
|
|
60
172
|
return body;
|
|
61
173
|
}
|
|
62
174
|
|
|
63
|
-
async _request(path, method = 'GET', body = null, params = null) {
|
|
175
|
+
async _request(path, method = 'GET', body = null, params = null, options = {}) {
|
|
64
176
|
let url = `${this.baseUrl}${path}`;
|
|
65
177
|
if (params) {
|
|
66
178
|
const qs = new URLSearchParams(params).toString();
|
|
@@ -78,9 +190,16 @@ class DashClaw {
|
|
|
78
190
|
body: body ? JSON.stringify(body) : undefined
|
|
79
191
|
});
|
|
80
192
|
|
|
81
|
-
|
|
193
|
+
let data = {};
|
|
194
|
+
if (typeof res.text === 'function') {
|
|
195
|
+
const text = await res.text();
|
|
196
|
+
data = text ? JSON.parse(text) : {};
|
|
197
|
+
} else if (typeof res.json === 'function') {
|
|
198
|
+
data = await res.json();
|
|
199
|
+
}
|
|
200
|
+
const allowedStatuses = new Set(options.allowStatuses || []);
|
|
82
201
|
|
|
83
|
-
if (!res.ok) {
|
|
202
|
+
if (!res.ok && !allowedStatuses.has(res.status)) {
|
|
84
203
|
if (res.status === 403 && data.decision && data.decision.decision === 'block') {
|
|
85
204
|
throw new GuardBlockedError(data.decision);
|
|
86
205
|
}
|
|
@@ -136,6 +255,13 @@ class DashClaw {
|
|
|
136
255
|
return this._request(`/api/actions/${actionId}`, 'GET');
|
|
137
256
|
}
|
|
138
257
|
|
|
258
|
+
/**
|
|
259
|
+
* GET /api/actions/:id/trace — Fetch replay trace, assumptions, loops, and root-cause indicators.
|
|
260
|
+
*/
|
|
261
|
+
async getActionTrace(actionId) {
|
|
262
|
+
return this._request(`/api/actions/${actionId}/trace`, 'GET');
|
|
263
|
+
}
|
|
264
|
+
|
|
139
265
|
/**
|
|
140
266
|
* GET /api/actions?status=pending_approval — List actions awaiting approval.
|
|
141
267
|
*/
|
|
@@ -237,6 +363,125 @@ class DashClaw {
|
|
|
237
363
|
throw new Error(`Timed out waiting for approval of action ${actionId}`);
|
|
238
364
|
}
|
|
239
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Subscribe to real-time SSE events from the OSuite server.
|
|
368
|
+
* Uses fetch-based SSE parsing for Node 18+ compatibility.
|
|
369
|
+
*/
|
|
370
|
+
events({ reconnect = true, maxRetries = Infinity, retryInterval = 3000 } = {}) {
|
|
371
|
+
const url = `${this.baseUrl}/api/stream`;
|
|
372
|
+
const handlers = new Map();
|
|
373
|
+
let closed = false;
|
|
374
|
+
let controller = null;
|
|
375
|
+
let lastEventId = null;
|
|
376
|
+
let retryCount = 0;
|
|
377
|
+
|
|
378
|
+
const emit = (eventType, data) => {
|
|
379
|
+
const callbacks = handlers.get(eventType) || [];
|
|
380
|
+
for (const callback of callbacks) {
|
|
381
|
+
try {
|
|
382
|
+
callback(data);
|
|
383
|
+
} catch {
|
|
384
|
+
// Event handlers are intentionally isolated from stream health
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const connect = async () => {
|
|
390
|
+
controller = new AbortController();
|
|
391
|
+
const headers = { 'x-api-key': this.apiKey };
|
|
392
|
+
if (lastEventId) headers['last-event-id'] = lastEventId;
|
|
393
|
+
|
|
394
|
+
const res = await fetch(url, {
|
|
395
|
+
headers,
|
|
396
|
+
signal: controller.signal,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
if (!res.ok) {
|
|
400
|
+
throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
retryCount = 0;
|
|
404
|
+
|
|
405
|
+
const reader = res.body.getReader();
|
|
406
|
+
const decoder = new TextDecoder();
|
|
407
|
+
let buffer = '';
|
|
408
|
+
let currentEvent = null;
|
|
409
|
+
let currentData = '';
|
|
410
|
+
|
|
411
|
+
while (!closed) {
|
|
412
|
+
const { done, value } = await reader.read();
|
|
413
|
+
if (done) break;
|
|
414
|
+
buffer += decoder.decode(value, { stream: true });
|
|
415
|
+
|
|
416
|
+
const lines = buffer.split('\n');
|
|
417
|
+
buffer = lines.pop() || '';
|
|
418
|
+
|
|
419
|
+
for (const line of lines) {
|
|
420
|
+
if (line.startsWith('id: ')) {
|
|
421
|
+
lastEventId = line.slice(4).trim();
|
|
422
|
+
} else if (line.startsWith('event: ')) {
|
|
423
|
+
currentEvent = line.slice(7).trim();
|
|
424
|
+
} else if (line.startsWith('data: ')) {
|
|
425
|
+
currentData += line.slice(6);
|
|
426
|
+
} else if (line.startsWith(':')) {
|
|
427
|
+
continue;
|
|
428
|
+
} else if (line === '' && currentEvent) {
|
|
429
|
+
if (currentData) {
|
|
430
|
+
try {
|
|
431
|
+
emit(currentEvent, JSON.parse(currentData));
|
|
432
|
+
} catch {
|
|
433
|
+
// Ignore malformed frames to keep the stream alive
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
currentEvent = null;
|
|
437
|
+
currentData = '';
|
|
438
|
+
} else if (line === '') {
|
|
439
|
+
currentEvent = null;
|
|
440
|
+
currentData = '';
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const connectLoop = async () => {
|
|
447
|
+
while (!closed) {
|
|
448
|
+
try {
|
|
449
|
+
await connect();
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (closed) return;
|
|
452
|
+
emit('error', error);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (closed) return;
|
|
456
|
+
if (!reconnect || retryCount >= maxRetries) {
|
|
457
|
+
emit('error', new Error('SSE stream ended'));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
retryCount += 1;
|
|
462
|
+
emit('reconnecting', { attempt: retryCount, maxRetries });
|
|
463
|
+
await new Promise((resolve) => setTimeout(resolve, retryInterval));
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const connectionPromise = connectLoop();
|
|
468
|
+
|
|
469
|
+
const handle = {
|
|
470
|
+
on(eventType, callback) {
|
|
471
|
+
if (!handlers.has(eventType)) handlers.set(eventType, []);
|
|
472
|
+
handlers.get(eventType).push(callback);
|
|
473
|
+
return handle;
|
|
474
|
+
},
|
|
475
|
+
close() {
|
|
476
|
+
closed = true;
|
|
477
|
+
if (controller) controller.abort();
|
|
478
|
+
},
|
|
479
|
+
_promise: connectionPromise,
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
return handle;
|
|
483
|
+
}
|
|
484
|
+
|
|
240
485
|
/**
|
|
241
486
|
* POST /api/agents/heartbeat
|
|
242
487
|
*/
|
|
@@ -287,6 +532,112 @@ class DashClaw {
|
|
|
287
532
|
return this._request('/api/actions/signals');
|
|
288
533
|
}
|
|
289
534
|
|
|
535
|
+
/**
|
|
536
|
+
* GET /api/governance/proof — Fetch operator-facing or bundle-form governance proof.
|
|
537
|
+
*/
|
|
538
|
+
async getGovernanceProof({ actionId = null, format = null } = {}) {
|
|
539
|
+
return this._request('/api/governance/proof', 'GET', null, {
|
|
540
|
+
...(actionId ? { action_id: actionId } : {}),
|
|
541
|
+
...(format ? { format } : {}),
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* GET /api/governance/proof?format=bundle — Fetch machine-readable PCAA / proof bundle output.
|
|
547
|
+
*/
|
|
548
|
+
async getGovernanceProofBundle({ actionId = null } = {}) {
|
|
549
|
+
return this.getGovernanceProof({ actionId, format: 'bundle' });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* POST /api/governance/proof/verify — Verify one action attestation and proof bundle.
|
|
554
|
+
*/
|
|
555
|
+
async verifyGovernanceProof(actionId, { throwOnInvalid = false } = {}) {
|
|
556
|
+
return this._request(
|
|
557
|
+
'/api/governance/proof/verify',
|
|
558
|
+
'POST',
|
|
559
|
+
{ action_id: actionId },
|
|
560
|
+
null,
|
|
561
|
+
throwOnInvalid ? {} : { allowStatuses: [422] },
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* GET /api/compliance/evidence — Fetch live evidence aggregates for PCAA / compliance.
|
|
567
|
+
*/
|
|
568
|
+
async getComplianceEvidence({ window = '30d' } = {}) {
|
|
569
|
+
return this._request('/api/compliance/evidence', 'GET', null, { window });
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* GET /api/proof/export — Fetch a portable proof export by action or bundle id.
|
|
574
|
+
*/
|
|
575
|
+
async exportProof({ actionId = null, bundleId = null } = {}) {
|
|
576
|
+
if (!actionId && !bundleId) {
|
|
577
|
+
throw new Error('actionId or bundleId is required');
|
|
578
|
+
}
|
|
579
|
+
return this._request('/api/proof/export', 'GET', null, {
|
|
580
|
+
...(actionId ? { action_id: actionId } : {}),
|
|
581
|
+
...(bundleId ? { bundle_id: bundleId } : {}),
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Build the same PCAA health summary used by the workspace banner.
|
|
587
|
+
*/
|
|
588
|
+
async getPcaaHealth({ window = '30d', agentId = null } = {}) {
|
|
589
|
+
const [actionsStats, complianceEvidence] = await Promise.all([
|
|
590
|
+
this._request('/api/actions/stats', 'GET', null, {
|
|
591
|
+
...(agentId ? { agent_id: agentId } : {}),
|
|
592
|
+
}),
|
|
593
|
+
this.getComplianceEvidence({ window }),
|
|
594
|
+
]);
|
|
595
|
+
|
|
596
|
+
const evidence = complianceEvidence?.evidence || {};
|
|
597
|
+
const totalActions = Number(actionsStats?.total || 0);
|
|
598
|
+
const recordedActions = Number(evidence.action_records_total || 0);
|
|
599
|
+
const approvals = Number(evidence.approval_requests || actionsStats?.approval || 0);
|
|
600
|
+
const proofBundles = Number(evidence.proof_bundles_total || 0);
|
|
601
|
+
const completeBundles = Number(evidence.complete_proof_bundles || 0);
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
totalActions,
|
|
605
|
+
recordedActions,
|
|
606
|
+
approvals,
|
|
607
|
+
proofBundles,
|
|
608
|
+
completeBundles,
|
|
609
|
+
checkpointCoverage: toPercent(recordedActions, totalActions || recordedActions || 1),
|
|
610
|
+
approvalRate: toPercent(approvals, totalActions || approvals || 1),
|
|
611
|
+
evidenceCompletion: toPercent(completeBundles, proofBundles || completeBundles || 1),
|
|
612
|
+
window,
|
|
613
|
+
generatedAt: complianceEvidence?.generated_at || new Date().toISOString(),
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Fetch one action's PCAA view: action, trace, proof bundle, export bundle, and derived checkpoints.
|
|
619
|
+
*/
|
|
620
|
+
async getPcaaAction(actionId) {
|
|
621
|
+
const [actionPayload, trace, proofBundle, proofExport] = await Promise.all([
|
|
622
|
+
this.getAction(actionId),
|
|
623
|
+
this.getActionTrace(actionId),
|
|
624
|
+
this.getGovernanceProofBundle({ actionId }),
|
|
625
|
+
this.exportProof({ actionId }).catch((error) => {
|
|
626
|
+
if (error?.status === 404) return null;
|
|
627
|
+
throw error;
|
|
628
|
+
}),
|
|
629
|
+
]);
|
|
630
|
+
|
|
631
|
+
const action = actionPayload?.action || actionPayload || null;
|
|
632
|
+
return {
|
|
633
|
+
action,
|
|
634
|
+
trace,
|
|
635
|
+
proofBundle,
|
|
636
|
+
proofExport,
|
|
637
|
+
checkpoints: buildPcaaCheckpointStates({ action, trace, proofBundle }),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
290
641
|
/**
|
|
291
642
|
* GET /api/learning/analytics/velocity
|
|
292
643
|
*/
|
|
@@ -623,6 +974,15 @@ class DashClaw {
|
|
|
623
974
|
}
|
|
624
975
|
|
|
625
976
|
const OSuite = DashClaw;
|
|
977
|
+
const Osuite = DashClaw;
|
|
626
978
|
|
|
627
979
|
export default OSuite;
|
|
628
|
-
export {
|
|
980
|
+
export {
|
|
981
|
+
DashClaw,
|
|
982
|
+
OSuite,
|
|
983
|
+
Osuite,
|
|
984
|
+
ApprovalDeniedError,
|
|
985
|
+
GuardBlockedError,
|
|
986
|
+
PCAA_CHECKPOINTS,
|
|
987
|
+
buildPcaaCheckpointStates,
|
|
988
|
+
};
|
package/index.cjs
CHANGED
|
@@ -10,6 +10,113 @@
|
|
|
10
10
|
// We use a simplified bridge that forwards calls to the async ESM import
|
|
11
11
|
let _module;
|
|
12
12
|
|
|
13
|
+
const PCAA_CHECKPOINTS = [
|
|
14
|
+
{
|
|
15
|
+
id: 'pre_action_admissibility',
|
|
16
|
+
title: 'Pre-action admissibility',
|
|
17
|
+
summary: 'Evaluate whether the proposed action is allowed, simulated first, blocked, or approval-gated before side effects happen.',
|
|
18
|
+
sdkMethod: 'guard',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'action_open',
|
|
22
|
+
title: 'Action open',
|
|
23
|
+
summary: 'Create the portable action record that becomes the trust object for replay, scoring, and proof.',
|
|
24
|
+
sdkMethod: 'createAction',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'assumption_capture',
|
|
28
|
+
title: 'Assumption capture',
|
|
29
|
+
summary: 'Record what the runtime believed or depended on so operators can replay the reasoning boundary later.',
|
|
30
|
+
sdkMethod: 'recordAssumption',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'approval_checkpoint',
|
|
34
|
+
title: 'Approval checkpoint',
|
|
35
|
+
summary: 'Pause, wait, or externally hold execution when policy requires a human checkpoint.',
|
|
36
|
+
sdkMethod: 'waitForApproval',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'outcome_closure',
|
|
40
|
+
title: 'Outcome closure',
|
|
41
|
+
summary: 'Write the final result, evidence, and status so the action certificate closes cleanly.',
|
|
42
|
+
sdkMethod: 'updateOutcome',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function resolveRouteDecision({ action = null, proofBundle = null } = {}) {
|
|
47
|
+
return (
|
|
48
|
+
proofBundle?.route_decision
|
|
49
|
+
|| proofBundle?.action_certificate?.route_decision
|
|
50
|
+
|| action?.policy_snapshot?.effective_decision
|
|
51
|
+
|| null
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveOutcomeStatus(status) {
|
|
56
|
+
const normalized = String(status || '').toLowerCase();
|
|
57
|
+
if (['completed', 'failed', 'blocked', 'cancelled', 'denied'].includes(normalized)) return 'complete';
|
|
58
|
+
if (['running', 'pending', 'pending_approval'].includes(normalized)) return 'pending';
|
|
59
|
+
return 'inactive';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildPcaaCheckpointStates({ action = null, trace = null, proofBundle = null } = {}) {
|
|
63
|
+
const routeDecision = resolveRouteDecision({ action, proofBundle });
|
|
64
|
+
const assumptionCount = Number(
|
|
65
|
+
trace?.trace?.assumptions?.total
|
|
66
|
+
?? trace?.assumptions?.total
|
|
67
|
+
?? (Array.isArray(trace?.assumptions) ? trace.assumptions.length : 0),
|
|
68
|
+
);
|
|
69
|
+
const approvalTriggered = ['require_approval', 'require_dual_approval', 'simulate_first', 'warn', 'block']
|
|
70
|
+
.includes(String(routeDecision || '').toLowerCase())
|
|
71
|
+
|| String(action?.status || '').toLowerCase() === 'pending_approval';
|
|
72
|
+
const approvalComplete = Boolean(
|
|
73
|
+
action?.approved_by
|
|
74
|
+
|| action?.approved_at
|
|
75
|
+
|| (Array.isArray(proofBundle?.approvals) && proofBundle.approvals.length > 0),
|
|
76
|
+
);
|
|
77
|
+
const outcomeStatus = resolveOutcomeStatus(action?.status);
|
|
78
|
+
|
|
79
|
+
return PCAA_CHECKPOINTS.map((checkpoint) => {
|
|
80
|
+
if (checkpoint.id === 'pre_action_admissibility') {
|
|
81
|
+
return {
|
|
82
|
+
...checkpoint,
|
|
83
|
+
status: routeDecision ? 'complete' : 'inactive',
|
|
84
|
+
value: routeDecision || 'not evaluated',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (checkpoint.id === 'action_open') {
|
|
89
|
+
return {
|
|
90
|
+
...checkpoint,
|
|
91
|
+
status: action?.action_id ? 'complete' : 'inactive',
|
|
92
|
+
value: action?.action_id || '--',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (checkpoint.id === 'assumption_capture') {
|
|
97
|
+
return {
|
|
98
|
+
...checkpoint,
|
|
99
|
+
status: assumptionCount > 0 ? 'complete' : 'inactive',
|
|
100
|
+
value: assumptionCount > 0 ? `${assumptionCount} recorded` : 'none recorded',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (checkpoint.id === 'approval_checkpoint') {
|
|
105
|
+
return {
|
|
106
|
+
...checkpoint,
|
|
107
|
+
status: approvalComplete ? 'complete' : approvalTriggered ? 'active' : 'inactive',
|
|
108
|
+
value: approvalComplete ? 'approved' : approvalTriggered ? (routeDecision || 'active') : 'not required',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...checkpoint,
|
|
114
|
+
status: outcomeStatus,
|
|
115
|
+
value: action?.status || '--',
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
13
120
|
async function loadModule() {
|
|
14
121
|
if (!_module) {
|
|
15
122
|
_module = await import('./osuite.js');
|
|
@@ -67,4 +174,6 @@ module.exports = {
|
|
|
67
174
|
DashClaw: OSuiteProxy,
|
|
68
175
|
ApprovalDeniedError,
|
|
69
176
|
GuardBlockedError,
|
|
177
|
+
PCAA_CHECKPOINTS,
|
|
178
|
+
buildPcaaCheckpointStates,
|
|
70
179
|
};
|
package/osuite.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
default,
|
|
3
|
+
DashClaw,
|
|
4
|
+
OSuite,
|
|
5
|
+
Osuite,
|
|
6
|
+
ApprovalDeniedError,
|
|
7
|
+
GuardBlockedError,
|
|
8
|
+
PCAA_CHECKPOINTS,
|
|
9
|
+
buildPcaaCheckpointStates,
|
|
10
|
+
} from './dashclaw.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osuite",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "OSuite governance runtime for AI agents. Intercept, govern, and verify agent actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"main": "./index.cjs",
|
|
10
10
|
"module": "./osuite.js",
|
|
11
11
|
"bin": {
|
|
12
|
-
"osuite": "cli.js"
|
|
13
|
-
"dashclaw": "cli.js"
|
|
12
|
+
"osuite": "cli.js"
|
|
14
13
|
},
|
|
15
14
|
"exports": {
|
|
16
15
|
".": {
|
|
@@ -50,7 +49,6 @@
|
|
|
50
49
|
"node": ">=18.0.0"
|
|
51
50
|
},
|
|
52
51
|
"dependencies": {},
|
|
53
|
-
"devDependencies": {},
|
|
54
52
|
"scripts": {},
|
|
55
53
|
"sideEffects": false
|
|
56
54
|
}
|