From ae1d057771899f0c10619cf0e634d64c4a01e21e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 15:09:01 -0400 Subject: [PATCH 1/7] Replay helper receipt pass fixture on current main --- .../helper-receipts/preview_local_only_pass.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/fixtures/helper-receipts/preview_local_only_pass.json diff --git a/tests/fixtures/helper-receipts/preview_local_only_pass.json b/tests/fixtures/helper-receipts/preview_local_only_pass.json new file mode 100644 index 0000000..865cbd2 --- /dev/null +++ b/tests/fixtures/helper-receipts/preview_local_only_pass.json @@ -0,0 +1,14 @@ +{ + "schema": "sourceos.helper_causal_receipt.v0.1", + "event_type": "capability.request", + "event_id": "evt_fixture_preview_pass", + "timestamp": "2026-05-06T00:00:00Z", + "root_intent_id": "intent.preview.file.fixture", + "policy_profile": "preview.local_only.v1", + "requestor": "sourceos-pdf-renderer", + "capability": "network.egress", + "requested_service": "network", + "decision": "deny", + "classification": "expected_denial", + "data_accessed": false +} From 2f49c41cb5ff90fa55e2619d3081b2ac98c49987 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 15:11:14 -0400 Subject: [PATCH 2/7] Replay helper receipt local-only negative fixture on current main --- ...preview_local_only_fail_pasteboard_allowed.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/fixtures/helper-receipts/preview_local_only_fail_pasteboard_allowed.json diff --git a/tests/fixtures/helper-receipts/preview_local_only_fail_pasteboard_allowed.json b/tests/fixtures/helper-receipts/preview_local_only_fail_pasteboard_allowed.json new file mode 100644 index 0000000..4701778 --- /dev/null +++ b/tests/fixtures/helper-receipts/preview_local_only_fail_pasteboard_allowed.json @@ -0,0 +1,14 @@ +{ + "schema": "sourceos.helper_causal_receipt.v0.1", + "event_type": "capability.request", + "event_id": "evt_fixture_preview_fail", + "timestamp": "2026-05-06T00:00:01Z", + "root_intent_id": "intent.preview.file.fixture", + "policy_profile": "preview.local_only.v1", + "requestor": "sourceos-pdf-renderer", + "capability": "pasteboard.read", + "requested_service": "clipboard", + "decision": "allow", + "classification": "policy_regression", + "data_accessed": true +} From 11e5d83ac4b9f136ebd525adb495d4e46c7be0cf Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 16:01:52 -0400 Subject: [PATCH 3/7] Replay helper receipt web thumbnail fixture on current main --- .../web_thumbnail_pass_clipboard_denied.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/fixtures/helper-receipts/web_thumbnail_pass_clipboard_denied.json diff --git a/tests/fixtures/helper-receipts/web_thumbnail_pass_clipboard_denied.json b/tests/fixtures/helper-receipts/web_thumbnail_pass_clipboard_denied.json new file mode 100644 index 0000000..97c0f14 --- /dev/null +++ b/tests/fixtures/helper-receipts/web_thumbnail_pass_clipboard_denied.json @@ -0,0 +1,14 @@ +{ + "schema": "sourceos.helper_causal_receipt.v0.1", + "event_type": "capability.request", + "event_id": "evt_fixture_webthumb_pass", + "timestamp": "2026-05-06T00:00:02Z", + "root_intent_id": "intent.preview.web.fixture", + "policy_profile": "preview.web_thumbnail.local_only.v1", + "requestor": "sourceos-web-thumbnailer", + "capability": "pasteboard.read", + "requested_service": "pasteboard", + "decision": "deny", + "classification": "expected_denial", + "data_accessed": false +} From 06ea42aaf6bcba552a6c275fddc692a8b939590e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 16:05:09 -0400 Subject: [PATCH 4/7] Replay helper receipt missing intent fixture on current main --- .../helper_spawn_missing_root_intent_fail.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/fixtures/helper-receipts/helper_spawn_missing_root_intent_fail.json diff --git a/tests/fixtures/helper-receipts/helper_spawn_missing_root_intent_fail.json b/tests/fixtures/helper-receipts/helper_spawn_missing_root_intent_fail.json new file mode 100644 index 0000000..2723c7b --- /dev/null +++ b/tests/fixtures/helper-receipts/helper_spawn_missing_root_intent_fail.json @@ -0,0 +1,13 @@ +{ + "schema": "sourceos.helper_causal_receipt.v0.1", + "event_type": "helper.spawn", + "event_id": "evt_fixture_spawn_fail", + "timestamp": "2026-05-06T00:00:03Z", + "root_intent_id": "BROKEN", + "parent_event_id": null, + "parent_process": "sourceos-shell", + "child_process": "sourceos-helper", + "trigger": "ipc", + "spawn_reason": "fixture", + "policy_profile": "preview.local_only.v1" +} From 4883a936ebcc3fa7dd0877e4d65eb3503df949be Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 16:15:56 -0400 Subject: [PATCH 5/7] Replay helper receipt validator on current main --- tools/check_helper_receipts.py | 102 +++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 tools/check_helper_receipts.py diff --git a/tools/check_helper_receipts.py b/tools/check_helper_receipts.py new file mode 100644 index 0000000..ca23b15 --- /dev/null +++ b/tools/check_helper_receipts.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Minimal CI policy gate for SourceOS Helper Causal Receipts. + +This intentionally checks high-value invariants first: +- every event has a root intent +- helper spawns declare parent/process/trigger/reason/policy +- capability requests declare decision/classification/data_accessed +- local-only profiles do not allow network, DNS, analytics, pasteboard, or account lookup +""" + +from __future__ import annotations + +from pathlib import Path +import argparse +import json +import sys +from typing import Iterable + +LOCAL_ONLY_PROFILES = { + "preview.local_only.v1", + "preview.web_thumbnail.local_only.v1", + "terminal.preview.local_only.v1", + "cache_cleanup.local_only.v1", + "file_picker.native_ui.v1", +} + +DENY_IN_LOCAL_ONLY = { + "network.egress", + "dns.lookup", + "analytics.emit", + "pasteboard.read", + "pasteboard.write", + "account.lookup", + "credentials.keychain.lookup", +} + + +def iter_events(path: Path) -> Iterable[dict]: + if path.suffix == ".jsonl": + for line in path.read_text(errors="replace").splitlines(): + if line.strip(): + yield json.loads(line) + else: + data = json.loads(path.read_text(errors="replace")) + if isinstance(data, list): + yield from data + else: + yield data + + +def check_event(ev: dict) -> list[str]: + errors: list[str] = [] + + if not str(ev.get("root_intent_id", "")).startswith("intent."): + errors.append("root_intent_id must start with 'intent.'.") + + event_type = ev.get("event_type") + + if event_type == "helper.spawn": + for field in ["parent_process", "child_process", "trigger", "spawn_reason", "policy_profile"]: + if field not in ev or ev.get(field) in (None, ""): + errors.append(f"helper.spawn missing {field}") + + if event_type == "capability.request": + for field in ["requestor", "capability", "requested_service", "decision", "classification", "data_accessed"]: + if field not in ev: + errors.append(f"capability.request missing {field}") + + if ev.get("policy_profile") in LOCAL_ONLY_PROFILES: + if ev.get("capability") in DENY_IN_LOCAL_ONLY and ev.get("decision") == "allow": + errors.append( + f"local-only policy {ev.get('policy_profile')} allowed forbidden capability {ev.get('capability')}" + ) + + if event_type == "helper.exit": + for field in ["process", "exit_status", "duration_ms", "receipt_complete"]: + if field not in ev: + errors.append(f"helper.exit missing {field}") + + return errors + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("paths", nargs="+", type=Path) + args = parser.parse_args() + + failures: list[dict] = [] + checked = 0 + + for path in args.paths: + for index, event in enumerate(iter_events(path)): + checked += 1 + for error in check_event(event): + failures.append({"path": str(path), "index": index, "error": error}) + + print(json.dumps({"checked": checked, "errors": failures}, indent=2, sort_keys=True)) + return 1 if failures else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From bfd902ca5b33cb659baadb88c90db7b8a61a7d88 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 16:19:55 -0400 Subject: [PATCH 6/7] Replay helper causal receipt schema on current main --- schemas/helper-causal-receipts.schema.json | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 schemas/helper-causal-receipts.schema.json diff --git a/schemas/helper-causal-receipts.schema.json b/schemas/helper-causal-receipts.schema.json new file mode 100644 index 0000000..f82106b --- /dev/null +++ b/schemas/helper-causal-receipts.schema.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sourceos.local/schemas/helper-causal-receipts.schema.json", + "title": "Helper Causal Receipts v0.1", + "type": "object", + "required": ["schema", "event_type", "event_id", "timestamp", "root_intent_id"], + "properties": { + "schema": { "const": "sourceos.helper_causal_receipt.v0.1" }, + "event_type": { + "enum": [ + "root_intent.created", + "helper.spawn", + "capability.request", + "helper.exit", + "teardown.normalized", + "policy.decision", + "data.touch" + ] + }, + "event_id": { "type": "string", "minLength": 8 }, + "parent_event_id": { "type": ["string", "null"] }, + "root_intent_id": { "type": "string", "pattern": "^intent\\." }, + "timestamp": { "type": "string" }, + "surface": { "type": ["string", "null"] }, + "actor": { "type": ["object", "null"], "additionalProperties": true }, + "declared_purpose": { "type": ["string", "null"] }, + "data_scope": { "type": ["object", "array", "null"] }, + "default_policy": { "type": ["string", "null"] }, + "receipt_required": { "type": ["boolean", "null"] }, + "parent_process": { "type": ["string", "null"] }, + "child_process": { "type": ["string", "null"] }, + "process": { "type": ["string", "null"] }, + "pid": { "type": ["integer", "null"] }, + "trigger": { "type": ["string", "null"] }, + "spawn_reason": { "type": ["string", "null"] }, + "policy_profile": { "type": ["string", "null"] }, + "expected_lifetime_ms": { "type": ["integer", "null"], "minimum": 0 }, + "capability_budget": { "type": ["object", "null"] }, + "requestor": { "type": ["string", "null"] }, + "capability": { "type": ["string", "null"] }, + "requested_service": { "type": ["string", "null"] }, + "decision": { "enum": ["allow", "deny", "degrade", "missing", "unknown", null] }, + "classification": { + "enum": [ + "expected_denial", + "unexpected_denial", + "compatibility_probe", + "policy_regression", + "malicious_probe_candidate", + "missing_service", + "teardown_race", + "duplicate_activation_coalesced", + "supervisor_worker_lifecycle_kill", + "unknown", + null + ] + }, + "policy_rule": { "type": ["string", "null"] }, + "data_accessed": { "type": ["boolean", "null"] }, + "exit_status": { "type": ["string", "null"] }, + "duration_ms": { "type": ["integer", "null"], "minimum": 0 }, + "children_cleaned": { "type": ["boolean", "null"] }, + "unexpected_denials": { "type": ["integer", "null"], "minimum": 0 }, + "network_used": { "type": ["boolean", "null"] }, + "receipt_complete": { "type": ["boolean", "null"] }, + "raw_message": { "type": ["string", "null"] }, + "normalized_class": { "type": ["string", "null"] }, + "severity": { "enum": ["trace", "info", "notice", "warning", "error", "critical", null] }, + "meaning": { "type": ["string", "null"] }, + "policy_impact": { "type": ["string", "null"] }, + "service_key": { "type": ["string", "null"] }, + "service_uuid": { "type": ["string", "null"] }, + "service_family_role": { "type": ["string", "null"] }, + "phase": { "type": ["string", "null"] }, + "line_no": { "type": ["integer", "null"] }, + "tags": { "type": ["array", "null"], "items": { "type": "string" } }, + "inference_confidence": { "enum": ["high", "medium", "low", "unknown", null] } + }, + "allOf": [ + { + "if": { "properties": { "event_type": { "const": "helper.spawn" } } }, + "then": { "required": ["parent_process", "child_process", "trigger", "spawn_reason", "policy_profile"] } + }, + { + "if": { "properties": { "event_type": { "const": "capability.request" } } }, + "then": { "required": ["requestor", "capability", "requested_service", "decision", "classification", "data_accessed"] } + }, + { + "if": { "properties": { "event_type": { "const": "helper.exit" } } }, + "then": { "required": ["process", "exit_status", "duration_ms", "receipt_complete"] } + } + ], + "additionalProperties": true +} From 39ccb3d4d76050ac4167e6cf7cf5189c6e5e676f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 16:23:00 -0400 Subject: [PATCH 7/7] Add helper causal receipts validation workflow on current main --- .github/workflows/helper-causal-receipts.yml | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/helper-causal-receipts.yml diff --git a/.github/workflows/helper-causal-receipts.yml b/.github/workflows/helper-causal-receipts.yml new file mode 100644 index 0000000..e298569 --- /dev/null +++ b/.github/workflows/helper-causal-receipts.yml @@ -0,0 +1,44 @@ +name: Helper Causal Receipts + +on: + pull_request: + paths: + - 'schemas/helper-causal-receipts.schema.json' + - 'tests/fixtures/helper-receipts/**' + - 'tools/check_helper_receipts.py' + - '.github/workflows/helper-causal-receipts.yml' + push: + branches: + - main + paths: + - 'schemas/helper-causal-receipts.schema.json' + - 'tests/fixtures/helper-receipts/**' + - 'tools/check_helper_receipts.py' + - '.github/workflows/helper-causal-receipts.yml' + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Validate passing fixtures + run: | + python3 tools/check_helper_receipts.py \ + tests/fixtures/helper-receipts/preview_local_only_pass.json \ + tests/fixtures/helper-receipts/web_thumbnail_pass_clipboard_denied.json + - name: Validate local-only negative fixture fails + run: | + if python3 tools/check_helper_receipts.py tests/fixtures/helper-receipts/preview_local_only_fail_pasteboard_allowed.json; then + echo 'negative fixture unexpectedly passed' + exit 1 + fi + - name: Validate missing-intent negative fixture fails + run: | + if python3 tools/check_helper_receipts.py tests/fixtures/helper-receipts/helper_spawn_missing_root_intent_fail.json; then + echo 'missing-intent fixture unexpectedly passed' + exit 1 + fi