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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 DashClaw
3
+ Copyright (c) 2026 Osuite
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # OSuite SDK (v2.8.0)
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 (v2.5.0)
73
+ ## SDK Surface Area
74
74
 
75
- The v2 SDK exposes **45 methods** optimized for stability and zero-overhead governance:
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
- DASHCLAW_BASE_URL Required, your OSuite base URL
18
- DASHCLAW_API_KEY Required, admin API key for approval operations
19
- DASHCLAW_AGENT_ID Optional, defaults to "osuite-cli"
20
-
21
- Compatibility:
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.DASHCLAW_BASE_URL;
51
- const apiKey = process.env.DASHCLAW_API_KEY;
52
- const agentId = process.env.DASHCLAW_AGENT_ID || 'osuite-cli';
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 DASHCLAW_BASE_URL or DASHCLAW_API_KEY.\n');
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
- const data = await res.json();
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 { DashClaw, OSuite, ApprovalDeniedError, GuardBlockedError };
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 { default, DashClaw, OSuite, ApprovalDeniedError, GuardBlockedError } from './dashclaw.js';
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.8.0",
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
  }