@aiwg/cockpit 2026.8.5 → 2026.8.7

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/README.md CHANGED
@@ -411,7 +411,11 @@ safe version/build fields in the report. When the executor build does not expose
411
411
  that metadata, set `AIWG_COCKPIT_EXECUTOR_VERSION=<tag-or-commit>` so release
412
412
  evidence still names the tested agentic-sandbox build. A manual run against
413
413
  agentic-sandbox `v2026.6.15` or newer should attach its markdown/JSON result to
414
- epic roctinam/aiwg#1588 before the epic is considered done-done.
414
+ epic roctinam/aiwg#1588 before the epic is considered done-done. The latest
415
+ immutable fleet/activity qualification is
416
+ [Agentic Sandbox v2026.8.3](../../docs/cockpit/qualifications/agentic-sandbox-v2026.8.3.md);
417
+ older v2026.7.x entries below are historical feature-floor evidence, not the
418
+ latest whole-integration claim.
415
419
 
416
420
  The executable release-validation procedure is
417
421
  `.aiwg/testing/cockpit-real-integration-uat-runbook.md`. Use that runbook for
@@ -575,7 +579,7 @@ agentic-sandbox executor through `AIWG_COCKPIT_EXECUTOR_URL`. The host target
575
579
  (agentic-sandbox#461) have landed upstream; the Bridge seam is now the
576
580
  AIWG-side integration point for #1589. Runtime-tier provisioning and
577
581
  host-daemon surfacing (roctinam/aiwg#1615) and transport-trust visibility (#1618)
578
- **landed and are verified against `v2026.7.4`** — transport posture and
582
+ **landed with a compatibility floor verified against `v2026.7.4`** — transport posture and
579
583
  host-daemon now render per instance (a host-daemon *detail-status* payload
580
584
  remains a residual under #1615). Direct/managed PTY negotiation (#1616) and the
581
585
  live real-sandbox gate (#1617) continue. Secure transport details map back to agentic-sandbox#409/#410/#412; local
@@ -0,0 +1,70 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/jmagly/agentic-sandbox/blob/main/docs/schemas/activity-event-v1.schema.json",
4
+ "title": "Agentic Sandbox Activity Event v1",
5
+ "description": "Stable metadata-first, loss-aware activity event envelope.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema_version", "event_id", "event_name", "plane", "occurred_at", "observed_at", "source", "correlation", "sensitivity", "retention_class", "payload", "integrity"],
9
+ "properties": {
10
+ "schema_version": {"const": "activity.event/v1"},
11
+ "event_id": {"type": "string", "format": "uuid", "description": "Source-generated UUIDv7."},
12
+ "event_name": {"type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"},
13
+ "plane": {"enum": ["session", "action", "network", "runtime", "system", "integrity"]},
14
+ "occurred_at": {"type": "string", "format": "date-time"},
15
+ "observed_at": {"type": "string", "format": "date-time"},
16
+ "source": {"$ref": "#/$defs/source"},
17
+ "correlation": {"$ref": "#/$defs/correlation"},
18
+ "actor": {"$ref": "#/$defs/entity"},
19
+ "target": {"$ref": "#/$defs/entity"},
20
+ "outcome": {"$ref": "#/$defs/outcome"},
21
+ "sensitivity": {"enum": ["metadata", "restricted-content", "secret-prohibited"]},
22
+ "retention_class": {"enum": ["standard", "security", "forensic-hold", "ephemeral"]},
23
+ "payload": {"type": "object", "description": "Event-specific allowlisted metadata after source filtering."},
24
+ "integrity": {"$ref": "#/$defs/integrity"}
25
+ },
26
+ "$defs": {
27
+ "source": {
28
+ "type": "object", "additionalProperties": false,
29
+ "required": ["collector", "layer", "runtime", "trust"],
30
+ "properties": {
31
+ "collector": {"type": "string", "minLength": 1},
32
+ "layer": {"enum": ["guest", "runtime", "host", "control-plane", "provider"]},
33
+ "runtime": {"enum": ["qemu-kvm", "cloud-hypervisor", "docker", "host", "unknown"]},
34
+ "trust": {"enum": ["observed", "attested", "self-reported", "derived"]},
35
+ "clock_id": {"type": "string"},
36
+ "clock_error_ms": {"type": "number", "minimum": 0}
37
+ }
38
+ },
39
+ "correlation": {
40
+ "type": "object", "additionalProperties": false,
41
+ "required": ["tenant_id", "host_id", "instance_id", "agent_id"],
42
+ "properties": {
43
+ "tenant_id": {"type": "string", "minLength": 1}, "host_id": {"type": "string", "minLength": 1},
44
+ "instance_id": {"type": "string", "minLength": 1}, "agent_id": {"type": "string", "minLength": 1},
45
+ "session_id": {"type": "string"}, "mission_id": {"type": "string"}, "task_id": {"type": "string"},
46
+ "tool_call_id": {"type": "string"}, "command_id": {"type": "string"}, "process_id": {"type": "string"},
47
+ "parent_event_id": {"type": "string", "format": "uuid"},
48
+ "trace_id": {"type": "string", "pattern": "^[0-9a-f]{32}$"},
49
+ "span_id": {"type": "string", "pattern": "^[0-9a-f]{16}$"}
50
+ }
51
+ },
52
+ "entity": {
53
+ "type": "object", "additionalProperties": false, "required": ["type", "id"],
54
+ "properties": {"type": {"type": "string"}, "id": {"type": "string"}, "uid": {"type": "integer", "minimum": 0}, "gid": {"type": "integer", "minimum": 0}, "pid": {"type": "integer", "minimum": 0}}
55
+ },
56
+ "outcome": {
57
+ "type": "object", "additionalProperties": false, "required": ["status"],
58
+ "properties": {"status": {"enum": ["started", "success", "failure", "allowed", "denied", "degraded", "unknown"]}, "exit_code": {"type": "integer"}, "reason": {"type": "string"}}
59
+ },
60
+ "integrity": {
61
+ "type": "object", "additionalProperties": false, "required": ["collector_sequence"],
62
+ "properties": {
63
+ "collector_sequence": {"type": "integer", "minimum": 1},
64
+ "source_hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, "source_previous_hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
65
+ "timeline_hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, "timeline_previous_hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
66
+ "signature": {"type": "string"}, "key_id": {"type": "string"}
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://agentic-sandbox.local/schemas/activity-operational-evidence-v1.schema.json",
4
+ "title": "Agentic Sandbox daily operational evidence record v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schema_version",
9
+ "record_date",
10
+ "sealed_at",
11
+ "window",
12
+ "identity_digests",
13
+ "input_digest",
14
+ "previous_record_digest",
15
+ "summary",
16
+ "record_digest"
17
+ ],
18
+ "properties": {
19
+ "schema_version": {
20
+ "const": "agentic.activity-operational-evidence/v1"
21
+ },
22
+ "record_date": {
23
+ "type": "string",
24
+ "format": "date"
25
+ },
26
+ "sealed_at": {
27
+ "type": "string",
28
+ "format": "date-time"
29
+ },
30
+ "window": {
31
+ "type": "object",
32
+ "additionalProperties": false,
33
+ "required": ["start", "end", "actual_span_seconds"],
34
+ "properties": {
35
+ "start": {"type": "string", "format": "date-time"},
36
+ "end": {"type": "string", "format": "date-time"},
37
+ "actual_span_seconds": {"type": "number", "minimum": 0}
38
+ }
39
+ },
40
+ "identity_digests": {
41
+ "type": "array",
42
+ "items": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
43
+ "minItems": 1,
44
+ "uniqueItems": true
45
+ },
46
+ "input_digest": {
47
+ "type": "string",
48
+ "pattern": "^[0-9a-f]{64}$"
49
+ },
50
+ "previous_record_digest": {
51
+ "oneOf": [
52
+ {"type": "null"},
53
+ {"type": "string", "pattern": "^[0-9a-f]{64}$"}
54
+ ]
55
+ },
56
+ "summary": {
57
+ "type": "object",
58
+ "required": [
59
+ "sample_count",
60
+ "evidence_class_counts",
61
+ "source_availability",
62
+ "latency_ms",
63
+ "metrics"
64
+ ],
65
+ "properties": {
66
+ "sample_count": {"type": "integer", "minimum": 1},
67
+ "evidence_class_counts": {
68
+ "type": "object",
69
+ "additionalProperties": false,
70
+ "required": ["organic", "canary", "drill"],
71
+ "properties": {
72
+ "organic": {"type": "integer", "minimum": 0},
73
+ "canary": {"type": "integer", "minimum": 0},
74
+ "drill": {"type": "integer", "minimum": 0}
75
+ }
76
+ },
77
+ "source_availability": {"type": "object"},
78
+ "latency_ms": {"type": "object"},
79
+ "metrics": {"type": "object"}
80
+ },
81
+ "additionalProperties": false
82
+ },
83
+ "record_digest": {
84
+ "type": "string",
85
+ "pattern": "^[0-9a-f]{64}$"
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,101 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ const EVENT_SCHEMA = JSON.parse(readFileSync(
4
+ new URL('../contracts/activity-event-v1.schema.json', import.meta.url),
5
+ 'utf8',
6
+ ));
7
+
8
+ const RESTRICTED_KEY = /(?:^|_)(?:content|terminal|prompt|environment|env|credential|secret|password|authorization|bearer|token|private_key|certificate|restricted_(?:url|uri|link))(?:$|_)/i;
9
+
10
+ function resolveRef(root, ref) {
11
+ if (!ref.startsWith('#/')) throw new Error(`unsupported external schema reference: ${ref}`);
12
+ return ref.slice(2).split('/').reduce((value, segment) => value?.[segment.replaceAll('~1', '/').replaceAll('~0', '~')], root);
13
+ }
14
+
15
+ function typeMatches(value, type) {
16
+ if (type === 'null') return value === null;
17
+ if (type === 'array') return Array.isArray(value);
18
+ if (type === 'object') return value !== null && typeof value === 'object' && !Array.isArray(value);
19
+ if (type === 'integer') return Number.isInteger(value);
20
+ if (type === 'number') return typeof value === 'number' && Number.isFinite(value);
21
+ return typeof value === type;
22
+ }
23
+
24
+ function formatMatches(value, format) {
25
+ if (format === 'uuid') return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
26
+ if (format === 'date') return /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T00:00:00Z`));
27
+ if (format === 'date-time') return !Number.isNaN(Date.parse(value));
28
+ return true;
29
+ }
30
+
31
+ function validateNode(value, schema, root, path, errors) {
32
+ if (schema.$ref) return validateNode(value, resolveRef(root, schema.$ref), root, path, errors);
33
+ if (schema.oneOf) {
34
+ const matches = schema.oneOf.filter((candidate) => {
35
+ const candidateErrors = [];
36
+ validateNode(value, candidate, root, path, candidateErrors);
37
+ return candidateErrors.length === 0;
38
+ });
39
+ if (matches.length !== 1) errors.push(`${path} must match exactly one schema`);
40
+ return;
41
+ }
42
+ if (schema.const !== undefined && value !== schema.const) errors.push(`${path} must equal ${JSON.stringify(schema.const)}`);
43
+ if (schema.enum && !schema.enum.includes(value)) errors.push(`${path} is not an allowed value`);
44
+ if (schema.type && !typeMatches(value, schema.type)) {
45
+ errors.push(`${path} must be ${schema.type}`);
46
+ return;
47
+ }
48
+ if (typeof value === 'string') {
49
+ if (schema.minLength !== undefined && value.length < schema.minLength) errors.push(`${path} is too short`);
50
+ if (schema.pattern && !new RegExp(schema.pattern, 'u').test(value)) errors.push(`${path} has invalid format`);
51
+ if (schema.format && !formatMatches(value, schema.format)) errors.push(`${path} has invalid ${schema.format} format`);
52
+ }
53
+ if (typeof value === 'number' && schema.minimum !== undefined && value < schema.minimum) errors.push(`${path} is below minimum`);
54
+ if (Array.isArray(value)) {
55
+ if (schema.minItems !== undefined && value.length < schema.minItems) errors.push(`${path} has too few items`);
56
+ if (schema.uniqueItems && new Set(value.map((item) => JSON.stringify(item))).size !== value.length) errors.push(`${path} items must be unique`);
57
+ if (schema.items) value.forEach((item, index) => validateNode(item, schema.items, root, `${path}/${index}`, errors));
58
+ }
59
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
60
+ for (const key of schema.required ?? []) if (!(key in value)) errors.push(`${path}/${key} is required`);
61
+ if (schema.additionalProperties === false) {
62
+ for (const key of Object.keys(value)) if (!(key in (schema.properties ?? {}))) errors.push(`${path}/${key} is not allowed`);
63
+ }
64
+ for (const [key, childSchema] of Object.entries(schema.properties ?? {})) {
65
+ if (key in value) validateNode(value[key], childSchema, root, `${path}/${key}`, errors);
66
+ }
67
+ }
68
+ }
69
+
70
+ function containsRestrictedField(value) {
71
+ if (Array.isArray(value)) return value.some(containsRestrictedField);
72
+ if (!value || typeof value !== 'object') return false;
73
+ return Object.entries(value).some(([key, child]) => RESTRICTED_KEY.test(key) || containsRestrictedField(child));
74
+ }
75
+
76
+ export function validateActivityEvent(event, expectedScope = undefined) {
77
+ const errors = [];
78
+ validateNode(event, EVENT_SCHEMA, EVENT_SCHEMA, '$', errors);
79
+ if (event?.sensitivity !== 'metadata') errors.push('$/sensitivity must be metadata at the Cockpit boundary');
80
+ if (containsRestrictedField(event)) errors.push('$ contains a restricted field');
81
+ if (expectedScope) {
82
+ for (const [key, value] of Object.entries(expectedScope)) {
83
+ if (event?.correlation?.[key] !== value) errors.push(`$/correlation/${key} scope mismatch`);
84
+ }
85
+ }
86
+ return { valid: errors.length === 0, errors };
87
+ }
88
+
89
+ export function assertActivityEvent(event, expectedScope = undefined) {
90
+ const result = validateActivityEvent(event, expectedScope);
91
+ if (!result.valid) {
92
+ const scopeMismatch = result.errors.some((error) => error.includes('scope mismatch'));
93
+ const restricted = result.errors.some((error) => error.includes('restricted') || error.includes('sensitivity'));
94
+ const error = new Error(result.errors.join('; '));
95
+ error.code = scopeMismatch ? 'activity_scope_mismatch' : restricted ? 'activity_restricted_data' : 'activity_malformed_envelope';
96
+ throw error;
97
+ }
98
+ return event;
99
+ }
100
+
101
+ export { EVENT_SCHEMA };
@@ -16,6 +16,7 @@ import { homedir } from 'node:os';
16
16
  import { fileURLToPath } from 'node:url';
17
17
  import { dirname, join, basename, extname, resolve, sep } from 'node:path';
18
18
  import { storeCockpitToken } from '../../shell-core/keychain.mjs';
19
+ import { assertActivityEvent } from './activity-contract.mjs';
19
20
 
20
21
  const __dir = dirname(fileURLToPath(import.meta.url));
21
22
  // Primary seam for roctinam/aiwg#1589: Cockpit talks to a real agentic-sandbox
@@ -1606,7 +1607,6 @@ const ACTIVITY_SCOPE_HEADERS = {
1606
1607
  instance_id: 'x-agentic-instance-id', agent_id: 'x-agentic-agent-id',
1607
1608
  };
1608
1609
  const ACTIVITY_FILTERS = new Set(['event_name', 'collector', 'trust', 'plane', 'outcome', 'session_id', 'mission_id', 'task_id', 'tool_call_id', 'command_id', 'process_id', 'trace_id', 'since', 'until', 'limit']);
1609
- const RESTRICTED_ACTIVITY_KEY = /(?:^|_)(?:content|terminal|prompt|environment|env|credential|secret|password|authorization|bearer|token|private_key|certificate|restricted_(?:url|uri|link))(?:$|_)/i;
1610
1610
 
1611
1611
  export function activityRequest(input = {}) {
1612
1612
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw Object.assign(new Error('activity request must be an object'), { code: 'activity_invalid_request' });
@@ -1630,12 +1630,6 @@ export function activityRequest(input = {}) {
1630
1630
  return { headers, scope, filter };
1631
1631
  }
1632
1632
 
1633
- function hasRestrictedActivityField(value) {
1634
- if (Array.isArray(value)) return value.some(hasRestrictedActivityField);
1635
- if (!value || typeof value !== 'object') return false;
1636
- return Object.entries(value).some(([key, child]) => RESTRICTED_ACTIVITY_KEY.test(key) || hasRestrictedActivityField(child));
1637
- }
1638
-
1639
1633
  export function validateActivityEnvelope(body, expectedScope, { includeEvents = false, exportEnvelope = false } = {}) {
1640
1634
  if (!body || typeof body !== 'object' || Array.isArray(body)) throw Object.assign(new Error('malformed activity envelope'), { code: 'activity_malformed_envelope' });
1641
1635
  if (!exportEnvelope && body.schema_version !== 'activity.event/v1') throw Object.assign(new Error('unsupported activity schema'), { code: 'activity_malformed_envelope' });
@@ -1664,14 +1658,18 @@ export function validateActivityEnvelope(body, expectedScope, { includeEvents =
1664
1658
  throw Object.assign(new Error('activity envelope has malformed collector coverage'), { code: 'activity_malformed_envelope' });
1665
1659
  }
1666
1660
  for (const event of events) {
1667
- if (event?.schema_version !== 'activity.event/v1' || event?.sensitivity !== 'metadata' || hasRestrictedActivityField(event)) {
1668
- throw Object.assign(new Error('activity envelope contains restricted or unsupported event data'), { code: 'activity_restricted_data' });
1669
- }
1670
- for (const [key, value] of Object.entries(expectedScope)) {
1671
- if (event?.correlation?.[key] !== value) throw Object.assign(new Error('activity event scope mismatch'), { code: 'activity_scope_mismatch' });
1672
- }
1673
- }
1674
- if (exportEnvelope && (!body.manifest || typeof body.manifest.key_id !== 'string' || typeof body.manifest.merkle_root !== 'string')) {
1661
+ assertActivityEvent(event, expectedScope);
1662
+ }
1663
+ const manifest = body.manifest;
1664
+ if (exportEnvelope && (!manifest
1665
+ || typeof manifest.batch_id !== 'string' || !manifest.batch_id
1666
+ || manifest.tenant_id !== expectedScope.tenant_id
1667
+ || typeof manifest.collector_id !== 'string' || !manifest.collector_id
1668
+ || !Number.isInteger(manifest.event_count) || manifest.event_count < 0
1669
+ || !/^[0-9a-f]{64}$/.test(manifest.merkle_root ?? '')
1670
+ || typeof manifest.key_id !== 'string' || !manifest.key_id
1671
+ || typeof manifest.signature !== 'string' || !manifest.signature
1672
+ || (manifest.previous_root !== null && manifest.previous_root !== undefined && !/^[0-9a-f]{64}$/.test(manifest.previous_root)))) {
1675
1673
  throw Object.assign(new Error('signed activity export has no valid manifest'), { code: 'activity_malformed_export' });
1676
1674
  }
1677
1675
  return body;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cockpit",
3
- "version": "2026.8.5",
3
+ "version": "2026.8.7",
4
4
  "description": "AIWG Cockpit — UX-first control plane over AIWG + multi-stack agentic sessions. Opt-in, separately published; NOT shipped in the base aiwg npm package (guarded by test/smoke/cockpit-base-footprint.test.js).",
5
5
  "type": "module",
6
6
  "license": "MIT",