From 19428e82f8bded84d5f3518dfa26b5bb411e6648 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 20:46:13 -0400 Subject: [PATCH 1/8] Add runtime effect decision schema --- schemas/runtime-effect-decision.v1.1.json | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 schemas/runtime-effect-decision.v1.1.json diff --git a/schemas/runtime-effect-decision.v1.1.json b/schemas/runtime-effect-decision.v1.1.json new file mode 100644 index 0000000..6e84bdd --- /dev/null +++ b/schemas/runtime-effect-decision.v1.1.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sourceos.dev/schemas/runtime-effect-decision.v1.1.json", + "title": "SourceOS Runtime Effect Decision v1.1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "decision_kind", + "decision_id", + "ts_decided", + "policy_decision_ref", + "event_ref", + "runtime_effect", + "effect_status", + "effect_scope", + "authority_mutation_performed", + "ledger_write_performed", + "evidence_refs" + ], + "properties": { + "schema_version": {"const": "1.1.0"}, + "decision_kind": {"const": "runtime-effect-decision"}, + "decision_id": {"type": "string", "minLength": 1}, + "ts_decided": {"type": "string", "format": "date-time"}, + "policy_decision_ref": {"type": "string", "minLength": 1}, + "event_ref": { + "type": "object", + "additionalProperties": false, + "required": ["event_id", "event_content_sha256"], + "properties": { + "event_id": {"type": "string", "minLength": 1}, + "event_content_sha256": {"$ref": "#/$defs/sha256"} + } + }, + "runtime_effect": { + "type": "string", + "enum": ["allow_dispatch", "deny_dispatch", "require_review", "redact_payload", "metadata_only", "export_ref_only", "quarantine", "block", "noop"] + }, + "effect_status": {"type": "string", "enum": ["admitted", "partial", "rejected", "requires_review", "failed_closed"]}, + "effect_scope": { + "type": "object", + "additionalProperties": false, + "required": ["component_ref", "operation", "side_effecting"], + "properties": { + "component_ref": {"type": "string", "minLength": 1}, + "operation": {"type": "string", "minLength": 1}, + "side_effecting": {"type": "boolean"} + } + }, + "authority_mutation_performed": {"const": false}, + "ledger_write_performed": {"const": false}, + "grant_state_decision_ref": {"type": ["string", "null"]}, + "ledger_record_ref": {"type": ["string", "null"]}, + "downstream_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "evidence_refs": {"type": "array", "minItems": 1, "items": {"type": "string"}, "uniqueItems": true}, + "explanation": {"type": "string"} + }, + "$defs": {"sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}} +} From 2334f802a7ea020b48effcf65392997a64e317d1 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 20:47:46 -0400 Subject: [PATCH 2/8] Add grant state decision schema --- schemas/grant-state-decision.v1.1.json | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 schemas/grant-state-decision.v1.1.json diff --git a/schemas/grant-state-decision.v1.1.json b/schemas/grant-state-decision.v1.1.json new file mode 100644 index 0000000..eb14966 --- /dev/null +++ b/schemas/grant-state-decision.v1.1.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sourceos.dev/schemas/grant-state-decision.v1.1.json", + "title": "SourceOS Grant State Decision v1.1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "decision_kind", + "decision_id", + "ts_decided", + "grant_ref", + "subject_ref", + "authority_decision", + "decision_actor_ref", + "authorization_policy_ref", + "authorization_evidence_refs", + "effective_at", + "authority_effects", + "source_policy_decision_ref", + "restoration_allowed" + ], + "properties": { + "schema_version": {"const": "1.1.0"}, + "decision_kind": {"const": "grant-state-decision"}, + "decision_id": {"type": "string", "minLength": 1}, + "ts_decided": {"type": "string", "format": "date-time"}, + "grant_ref": {"type": "string", "minLength": 1}, + "subject_ref": {"type": "string", "minLength": 1}, + "authority_decision": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]}, + "decision_actor_ref": {"type": "string", "minLength": 1}, + "authorization_policy_ref": {"type": "string", "minLength": 1}, + "authorization_evidence_refs": {"type": "array", "minItems": 1, "items": {"type": "string"}, "uniqueItems": true}, + "effective_at": {"type": "string", "format": "date-time"}, + "authority_effects": { + "type": "object", + "additionalProperties": false, + "required": ["tool_access", "memory_access", "event_write", "bridge_export", "runtime_dispatch"], + "properties": { + "tool_access": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]}, + "memory_access": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]}, + "event_write": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]}, + "bridge_export": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]}, + "runtime_dispatch": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]} + } + }, + "source_policy_decision_ref": {"type": ["string", "null"]}, + "source_runtime_effect_decision_ref": {"type": ["string", "null"]}, + "restoration_allowed": {"type": "boolean"}, + "restoration_conditions": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "evidence_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "explanation": {"type": "string"} + } +} From 007bedf65f53cfe84eb6ebab3048a7ff14dab838 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 20:49:55 -0400 Subject: [PATCH 3/8] Add runtime effect decision fixture --- examples/runtime-effect-decision.valid.json | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/runtime-effect-decision.valid.json diff --git a/examples/runtime-effect-decision.valid.json b/examples/runtime-effect-decision.valid.json new file mode 100644 index 0000000..577bf5b --- /dev/null +++ b/examples/runtime-effect-decision.valid.json @@ -0,0 +1,25 @@ +{ + "schema_version": "1.1.0", + "decision_kind": "runtime-effect-decision", + "decision_id": "runtime-effect-decision:demo-dispatch-001", + "ts_decided": "2026-05-27T00:50:00Z", + "policy_decision_ref": "policy-decision:demo-export-001", + "event_ref": { + "event_id": "sourceos-event:demo-001", + "event_content_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "runtime_effect": "export_ref_only", + "effect_status": "admitted", + "effect_scope": { + "component_ref": "sourceos-shell://receipt-export", + "operation": "export_receipt_ref", + "side_effecting": false + }, + "authority_mutation_performed": false, + "ledger_write_performed": false, + "grant_state_decision_ref": null, + "ledger_record_ref": null, + "downstream_refs": ["grant-state-decision:optional-followup"], + "evidence_refs": ["evidence://sourceos/runtime-effect/demo-001"], + "explanation": "Policy decision allows ref-only export; runtime effect admits only the ref export and performs no authority mutation." +} From e5cfd5447c87e13abc1d63c23b5bcfba0a43ce2c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 20:51:45 -0400 Subject: [PATCH 4/8] Add runtime effect authority mutation negative fixture --- ...ct-decision.authority-mutated.invalid.json | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/runtime-effect-decision.authority-mutated.invalid.json diff --git a/examples/runtime-effect-decision.authority-mutated.invalid.json b/examples/runtime-effect-decision.authority-mutated.invalid.json new file mode 100644 index 0000000..55d9d9c --- /dev/null +++ b/examples/runtime-effect-decision.authority-mutated.invalid.json @@ -0,0 +1,25 @@ +{ + "schema_version": "1.1.0", + "decision_kind": "runtime-effect-decision", + "decision_id": "runtime-effect-decision:invalid-authority-mutation", + "ts_decided": "2026-05-27T00:51:00Z", + "policy_decision_ref": "policy-decision:demo-export-001", + "event_ref": { + "event_id": "sourceos-event:demo-001", + "event_content_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "runtime_effect": "allow_dispatch", + "effect_status": "admitted", + "effect_scope": { + "component_ref": "agent-term://dispatch", + "operation": "dispatch_agent", + "side_effecting": true + }, + "authority_mutation_performed": true, + "ledger_write_performed": false, + "grant_state_decision_ref": "grant-state-decision:invalid-inline-mutation", + "ledger_record_ref": null, + "downstream_refs": [], + "evidence_refs": ["evidence://sourceos/runtime-effect/invalid-authority-mutation"], + "explanation": "Invalid fixture: runtime effect decisions must not mutate authority inline." +} From ef3cde9375555164206740991a0490a0c24ea8ed Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 20:54:22 -0400 Subject: [PATCH 5/8] Add grant state decision fixture --- examples/grant-state-decision.valid.json | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/grant-state-decision.valid.json diff --git a/examples/grant-state-decision.valid.json b/examples/grant-state-decision.valid.json new file mode 100644 index 0000000..ea5981d --- /dev/null +++ b/examples/grant-state-decision.valid.json @@ -0,0 +1,26 @@ +{ + "schema_version": "1.1.0", + "decision_kind": "grant-state-decision", + "decision_id": "grant-state-decision:agent-alpha-review-001", + "ts_decided": "2026-05-27T00:52:00Z", + "grant_ref": "agent-grant:agent-alpha-001", + "subject_ref": "agent://agent-alpha", + "authority_decision": "reduced", + "decision_actor_ref": "service://sourceos-policy-adjudicator", + "authorization_policy_ref": "policy://sourceos/grant-state-v1.1", + "authorization_evidence_refs": ["policy-decision:agent-alpha-review-001", "evidence://sourceos/grant-state/agent-alpha-review-001"], + "effective_at": "2026-05-27T00:53:00Z", + "authority_effects": { + "tool_access": "reduced", + "memory_access": "unchanged", + "event_write": "reduced", + "bridge_export": "suspended", + "runtime_dispatch": "reduced" + }, + "source_policy_decision_ref": "policy-decision:agent-alpha-review-001", + "source_runtime_effect_decision_ref": null, + "restoration_allowed": true, + "restoration_conditions": ["fresh policy pass", "grant not expired", "no revocation ref present"], + "evidence_refs": ["evidence://sourceos/grant-state/agent-alpha-review-001"], + "explanation": "Policy review requires reduced authority; grant state change is recorded separately from policy and runtime effect decisions." +} From dfad643f04ecc5ad70327646aa137118e25e2a54 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 21:26:33 -0400 Subject: [PATCH 6/8] Add grant state missing authorization negative fixture --- ...ecision.missing-authorization.invalid.json | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/grant-state-decision.missing-authorization.invalid.json diff --git a/examples/grant-state-decision.missing-authorization.invalid.json b/examples/grant-state-decision.missing-authorization.invalid.json new file mode 100644 index 0000000..969645c --- /dev/null +++ b/examples/grant-state-decision.missing-authorization.invalid.json @@ -0,0 +1,26 @@ +{ + "schema_version": "1.1.0", + "decision_kind": "grant-state-decision", + "decision_id": "grant-state-decision:missing-authorization-invalid", + "ts_decided": "2026-05-27T00:54:00Z", + "grant_ref": "agent-grant:agent-alpha-001", + "subject_ref": "agent://agent-alpha", + "authority_decision": "revoked", + "decision_actor_ref": "service://sourceos-policy-adjudicator", + "authorization_policy_ref": "", + "authorization_evidence_refs": [], + "effective_at": "2026-05-27T00:55:00Z", + "authority_effects": { + "tool_access": "revoked", + "memory_access": "revoked", + "event_write": "revoked", + "bridge_export": "revoked", + "runtime_dispatch": "revoked" + }, + "source_policy_decision_ref": "policy-decision:agent-alpha-revoke-001", + "source_runtime_effect_decision_ref": null, + "restoration_allowed": false, + "restoration_conditions": [], + "evidence_refs": ["evidence://sourceos/grant-state/missing-authorization-invalid"], + "explanation": "Invalid fixture: grant state changes require authorization policy and evidence refs." +} From 397e98d56cdafa743f3feb7a8caccb3326d67ab0 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 21:29:56 -0400 Subject: [PATCH 7/8] Add lifecycle boundary validator --- tools/validate_lifecycle_boundary_examples.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tools/validate_lifecycle_boundary_examples.py diff --git a/tools/validate_lifecycle_boundary_examples.py b/tools/validate_lifecycle_boundary_examples.py new file mode 100644 index 0000000..e8de288 --- /dev/null +++ b/tools/validate_lifecycle_boundary_examples.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import jsonschema + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME_SCHEMA = ROOT / "schemas" / "runtime-effect-decision.v1.1.json" +GRANT_SCHEMA = ROOT / "schemas" / "grant-state-decision.v1.1.json" +RUNTIME_VALID = ROOT / "examples" / "runtime-effect-decision.valid.json" +RUNTIME_INVALID_AUTHORITY = ROOT / "examples" / "runtime-effect-decision.authority-mutated.invalid.json" +GRANT_VALID = ROOT / "examples" / "grant-state-decision.valid.json" +GRANT_INVALID_MISSING_AUTH = ROOT / "examples" / "grant-state-decision.missing-authorization.invalid.json" + + +class ValidationError(Exception): + pass + + +def load(path: Path) -> dict[str, Any]: + payload = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(payload, dict): + raise ValidationError(f"{path}: expected JSON object") + return payload + + +def validate_json_schema(schema_path: Path, instance_path: Path) -> dict[str, Any]: + schema = load(schema_path) + jsonschema.validators.validator_for(schema).check_schema(schema) + instance = load(instance_path) + jsonschema.validate(instance, schema) + return instance + + +def validate_runtime_effect(instance: dict[str, Any]) -> None: + if instance.get("decision_kind") != "runtime-effect-decision": + raise ValidationError("runtime effect decision_kind mismatch") + if instance.get("authority_mutation_performed") is not False: + raise ValidationError("runtime effect decisions must not mutate authority") + if instance.get("ledger_write_performed") is not False: + raise ValidationError("runtime effect decisions must not write ledger records") + effect = instance.get("runtime_effect") + status = instance.get("effect_status") + scope = instance.get("effect_scope", {}) + if effect in {"allow_dispatch", "export_ref_only"} and status not in {"admitted", "partial"}: + raise ValidationError(f"{effect} requires admitted or partial status") + if effect in {"block", "quarantine", "deny_dispatch"} and status == "admitted": + raise ValidationError(f"{effect} cannot report admitted status") + if scope.get("side_effecting") is True and effect in {"metadata_only", "export_ref_only", "noop"}: + raise ValidationError("metadata/ref/noop runtime effects cannot be side-effecting") + if instance.get("grant_state_decision_ref") and instance.get("authority_mutation_performed") is not False: + raise ValidationError("grant_state_decision_ref is a reference, not inline authority mutation") + + +def validate_grant_state(instance: dict[str, Any]) -> None: + if instance.get("decision_kind") != "grant-state-decision": + raise ValidationError("grant state decision_kind mismatch") + if not instance.get("authorization_policy_ref"): + raise ValidationError("grant state decisions require authorization_policy_ref") + if not instance.get("authorization_evidence_refs"): + raise ValidationError("grant state decisions require authorization_evidence_refs") + decision = instance.get("authority_decision") + effects = instance.get("authority_effects", {}) + changed = any(value != "unchanged" for value in effects.values()) + if decision == "unchanged" and changed: + raise ValidationError("unchanged grant state decision requires unchanged authority_effects") + if decision != "unchanged" and not changed: + raise ValidationError("changed grant state decision requires changed authority_effects") + if decision == "revoked" and any(value != "revoked" for value in effects.values()): + raise ValidationError("revoked grant state decision requires all authority_effects revoked") + if decision == "restored" and instance.get("restoration_allowed") is not True: + raise ValidationError("restored grant state decision requires restoration_allowed=true") + + +def expect_invalid(schema_path: Path, instance_path: Path, semantic_validator) -> None: + try: + instance = validate_json_schema(schema_path, instance_path) + semantic_validator(instance) + except Exception: + return + raise ValidationError(f"invalid fixture unexpectedly validated: {instance_path.relative_to(ROOT)}") + + +def main() -> int: + runtime = validate_json_schema(RUNTIME_SCHEMA, RUNTIME_VALID) + validate_runtime_effect(runtime) + grant = validate_json_schema(GRANT_SCHEMA, GRANT_VALID) + validate_grant_state(grant) + expect_invalid(RUNTIME_SCHEMA, RUNTIME_INVALID_AUTHORITY, validate_runtime_effect) + expect_invalid(GRANT_SCHEMA, GRANT_INVALID_MISSING_AUTH, validate_grant_state) + print(json.dumps({"ok": True, "checks": [ + str(RUNTIME_VALID.relative_to(ROOT)), + str(GRANT_VALID.relative_to(ROOT)), + str(RUNTIME_INVALID_AUTHORITY.relative_to(ROOT)), + str(GRANT_INVALID_MISSING_AUTH.relative_to(ROOT)), + ]}, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 1120dae1d89e623c24f7439e05ba8d3be80f019a Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 26 May 2026 21:31:27 -0400 Subject: [PATCH 8/8] Validate lifecycle boundary examples --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0f9ee82..b001f00 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples +.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-lifecycle-boundary-examples -validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples +validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-lifecycle-boundary-examples @echo "OK: validate" validate-control-plane-examples: @@ -22,3 +22,7 @@ validate-ops-history-examples: validate-runtime-observability-examples: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_runtime_observability_examples.py + +validate-lifecycle-boundary-examples: + python3 -m pip install --user jsonschema >/dev/null + python3 tools/validate_lifecycle_boundary_examples.py