Skip to content
33 changes: 33 additions & 0 deletions asyncapi.diagnostics.patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Additive AsyncAPI patch fragment for diagnostics bundles.
# Merge this file into asyncapi.yaml at build time using @asyncapi/bundler.
# All channels use Kafka with the same broker configuration as the base spec.

channels:
srcos.v2.diagnostics.events:
description: Events emitted when a DiagnosticsBundle is recorded.
bindings:
kafka:
topic: srcos.v2.diagnostics.events
partitions: 3
replicas: 3
topicConfiguration:
retention.ms: 2592000000
cleanup.policy: delete
publish:
operationId: publishDiagnosticsBundleEvent
summary: Publish a diagnostics bundle domain event
description: Publishes an event whenever a DiagnosticsBundle is recorded.
message:
name: DiagnosticsBundleEvent
title: Diagnostics Bundle Domain Event
summary: A change event for a DiagnosticsBundle.
description: |
The `eventType` in the payload envelope is `DiagnosticsBundleRecorded`.
The payload contains the full DiagnosticsBundle object.
contentType: application/json
payload: { $ref: './schemas/EventEnvelope.json' }
bindings:
kafka:
key:
type: string
description: The `objectId` URN of the DiagnosticsBundle.
61 changes: 61 additions & 0 deletions docs/adr/ADR-diagnostics-bundle-evidence-artifact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ADR — Diagnostics bundle as evidence artifact

**Date:** 2026-04-16
**Status:** `Proposed`

---

## Context

SourceOS and SociOS already define machine-readable contracts for workloads, provenance, policies, and agent-session telemetry. What remains under-specified is the host-diagnostics capture artifact itself: a bounded bundle of timed probes, artifact hashes, and redaction posture that can be collected locally, diffed later, and exported safely when needed.

Without a canonical diagnostics object, downstream repos risk inventing incompatible formats for:

- timed command manifests
- privacy-preserving host identifiers
- raw-vs-share artifact separation
- bundle-level hashing and signing
- support and incident handoff between runtime, operator, and automation layers

## Decision

Introduce `DiagnosticsBundle` as a first-class execution/provenance contract in `SourceOS-Linux/sourceos-spec`, with supporting schemas for `DiagnosticsProbeRecord` and `DiagnosticsRedactionPolicy`.

The bundle contract standardizes:

1. bundle identity via `urn:srcos:diag-bundle:<local-id>`
2. probe-level timing, exit status, and artifact digests
3. declared redaction posture for raw and share-safe bundle forms
4. optional bundle-level integrity hashes and signatures
5. optional linkage upward to `RunRecord` URNs for runtime or workflow correlation

## Alternatives considered

| Alternative | Reason not chosen |
|-------------|------------------|
| Keep diagnostics as an implementation-only script format in a runtime repo | Would create a second implicit schema authority and make provenance, conformance, and interop harder. |
| Reuse `RunRecord` directly for diagnostics bundles | `RunRecord` captures workload execution, not a custody-oriented bundle of timed host probes and redaction metadata. |
| Put diagnostics contracts only in SociOS runtime repos | The typed contract layer belongs in `sourceos-spec`; runtime repos should consume, not redefine, the object semantics. |

## Consequences

### Positive

- creates a canonical evidence object for host diagnostics capture
- makes raw/share separation and masking posture explicit instead of ad hoc
- gives downstream runtime and packaging repos a stable object to produce and validate
- preserves alignment with existing execution/provenance and event-envelope patterns

### Negative / constraints

- this initial tranche is additive and intentionally narrow
- it does not yet update every catalog and semantic index file in the repo
- it does not yet define the runtime capability or immutable-image packaging surface

## References

- `schemas/DiagnosticsBundle.json`
- `schemas/DiagnosticsProbeRecord.json`
- `schemas/DiagnosticsRedactionPolicy.json`
- `openapi.diagnostics.patch.yaml`
- `asyncapi.diagnostics.patch.yaml`
100 changes: 100 additions & 0 deletions examples/diagnosticsbundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"id": "urn:srcos:diag-bundle:hosttag-20260416t034500z",
"type": "DiagnosticsBundle",
"specVersion": "2.0.0",
"generatedAt": "2026-04-16T03:45:00Z",
"hostTag": "a1b2c3d4e5f6",
"bootSelector": "-0",
"sinceWindow": "24 hours ago",
"tiersEnabled": [
0,
1
],
"capturedBy": {
"toolName": "socios-diagkit",
"toolVersion": "0.1.0"
},
"platform": {
"osRelease": "Fedora Linux 42.20260415.0 (Silverblue)",
"kernel": "6.13.8-200.fc42.x86_64"
},
"probeRecords": [
{
"name": "journal_boot",
"tier": 0,
"timeoutSeconds": 25,
"command": "journalctl --boot -0 --no-pager -o short-iso",
"exitCode": 0,
"timeoutHit": false,
"durationMs": 412,
"stdout": {
"path": "raw/0/journal_boot.out",
"bytes": 18442,
"sha256": "1111111111111111111111111111111111111111111111111111111111111111"
},
"stderr": {
"path": "raw/0/journal_boot.err",
"bytes": 0,
"sha256": "2222222222222222222222222222222222222222222222222222222222222222"
},
"rssMaxKb": 2048
},
{
"name": "ss_listen",
"tier": 0,
"timeoutSeconds": 5,
"command": "ss -tulpn",
"exitCode": 0,
"timeoutHit": false,
"durationMs": 37,
"stdout": {
"path": "raw/0/ss_listen.out",
"bytes": 2271,
"sha256": "3333333333333333333333333333333333333333333333333333333333333333"
},
"stderr": {
"path": "raw/0/ss_listen.err",
"bytes": 0,
"sha256": "4444444444444444444444444444444444444444444444444444444444444444"
},
"rssMaxKb": 512
}
],
"summary": {
"probeCount": 2,
"failedCount": 0,
"timeoutCount": 0
},
"artifacts": {
"rawTreeSha256": "5555555555555555555555555555555555555555555555555555555555555555",
"sharedTreeSha256": "6666666666666666666666666666666666666666666666666666666666666666",
"tarballSha256": "7777777777777777777777777777777777777777777777777777777777777777"
},
"redactionPolicy": {
"mode": "raw-and-share",
"rawArtifactRetained": true,
"sharedArtifactGenerated": true,
"deterministicMasking": true,
"maskedClasses": [
"authorizationBearer",
"cookie",
"apiKey",
"token",
"email",
"privateIp",
"macAddress"
],
"privateIpMasked": true,
"publicIpMasked": false,
"macAddressMasked": true,
"notes": "Default share-safe policy for operator export; public IPs remain visible for network triage unless explicitly overridden."
},
"relatedRunRefs": [
"urn:srcos:run:diag-capture-20260416"
],
"integrity": {
"manifestSha256": "8888888888888888888888888888888888888888888888888888888888888888",
"bundleHash": "9999999999999999999999999999999999999999999999999999999999999999",
"signature": "sigstore:rekor:example-entry-0001"
}
}
20 changes: 20 additions & 0 deletions examples/diagnosticsproberecord.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "journal_boot",
"tier": 0,
"timeoutSeconds": 25,
"command": "journalctl --boot -0 --no-pager -o short-iso",
"exitCode": 0,
"timeoutHit": false,
"durationMs": 412,
"stdout": {
"path": "raw/0/journal_boot.out",
"bytes": 18442,
"sha256": "1111111111111111111111111111111111111111111111111111111111111111"
},
"stderr": {
"path": "raw/0/journal_boot.err",
"bytes": 0,
"sha256": "2222222222222222222222222222222222222222222222222222222222222222"
},
"rssMaxKb": 2048
}
19 changes: 19 additions & 0 deletions examples/diagnosticsredactionpolicy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"mode": "raw-and-share",
"rawArtifactRetained": true,
"sharedArtifactGenerated": true,
"deterministicMasking": true,
"maskedClasses": [
"authorizationBearer",
"cookie",
"apiKey",
"token",
"email",
"privateIp",
"macAddress"
],
"privateIpMasked": true,
"publicIpMasked": false,
"macAddressMasked": true,
"notes": "Default share-safe policy for operator export; public IPs remain visible for network triage unless explicitly overridden."
}
31 changes: 31 additions & 0 deletions openapi.diagnostics.patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Additive OpenAPI patch fragment for diagnostics bundles.
# Merge this file into openapi.yaml at build time.
# All operations require bearerAuth (inherited from the base spec security requirement).

tags:
- name: Diagnostics
description: Governed host diagnostics evidence bundles and their supporting metadata

paths:
/v2/diagnostics/bundles:
post:
operationId: recordDiagnosticsBundle
summary: Record a DiagnosticsBundle
description: Records a governed host diagnostics evidence bundle containing timed probe metadata, artifact hashes, and declared redaction posture. Idempotent on `id`.
tags: [Diagnostics]
requestBody:
required: true
content:
application/json:
schema: { $ref: './schemas/DiagnosticsBundle.json' }
responses:
'200':
description: DiagnosticsBundle recorded successfully.
'400':
description: Request body is malformed JSON.
'401':
description: Missing or invalid authentication token.
'403':
description: Authenticated subject is not permitted to record diagnostics bundles.
'422':
description: Request body is valid JSON but fails schema validation.
Loading
Loading