Skip to content

Commit aa1908a

Browse files
authored
Add lifecycle boundary decision contracts (#114)
Add runtime-effect and grant-state decision schemas, examples, negative fixtures, and validator for SourceOS lifecycle-boundary discipline under #113.
1 parent adbcfb3 commit aa1908a

8 files changed

Lines changed: 326 additions & 2 deletions

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples
1+
.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
22

3-
validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples
3+
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
44
@echo "OK: validate"
55

66
validate-control-plane-examples:
@@ -22,3 +22,7 @@ validate-ops-history-examples:
2222
validate-runtime-observability-examples:
2323
python3 -m pip install --user jsonschema >/dev/null
2424
python3 tools/validate_runtime_observability_examples.py
25+
26+
validate-lifecycle-boundary-examples:
27+
python3 -m pip install --user jsonschema >/dev/null
28+
python3 tools/validate_lifecycle_boundary_examples.py
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"schema_version": "1.1.0",
3+
"decision_kind": "grant-state-decision",
4+
"decision_id": "grant-state-decision:missing-authorization-invalid",
5+
"ts_decided": "2026-05-27T00:54:00Z",
6+
"grant_ref": "agent-grant:agent-alpha-001",
7+
"subject_ref": "agent://agent-alpha",
8+
"authority_decision": "revoked",
9+
"decision_actor_ref": "service://sourceos-policy-adjudicator",
10+
"authorization_policy_ref": "",
11+
"authorization_evidence_refs": [],
12+
"effective_at": "2026-05-27T00:55:00Z",
13+
"authority_effects": {
14+
"tool_access": "revoked",
15+
"memory_access": "revoked",
16+
"event_write": "revoked",
17+
"bridge_export": "revoked",
18+
"runtime_dispatch": "revoked"
19+
},
20+
"source_policy_decision_ref": "policy-decision:agent-alpha-revoke-001",
21+
"source_runtime_effect_decision_ref": null,
22+
"restoration_allowed": false,
23+
"restoration_conditions": [],
24+
"evidence_refs": ["evidence://sourceos/grant-state/missing-authorization-invalid"],
25+
"explanation": "Invalid fixture: grant state changes require authorization policy and evidence refs."
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"schema_version": "1.1.0",
3+
"decision_kind": "grant-state-decision",
4+
"decision_id": "grant-state-decision:agent-alpha-review-001",
5+
"ts_decided": "2026-05-27T00:52:00Z",
6+
"grant_ref": "agent-grant:agent-alpha-001",
7+
"subject_ref": "agent://agent-alpha",
8+
"authority_decision": "reduced",
9+
"decision_actor_ref": "service://sourceos-policy-adjudicator",
10+
"authorization_policy_ref": "policy://sourceos/grant-state-v1.1",
11+
"authorization_evidence_refs": ["policy-decision:agent-alpha-review-001", "evidence://sourceos/grant-state/agent-alpha-review-001"],
12+
"effective_at": "2026-05-27T00:53:00Z",
13+
"authority_effects": {
14+
"tool_access": "reduced",
15+
"memory_access": "unchanged",
16+
"event_write": "reduced",
17+
"bridge_export": "suspended",
18+
"runtime_dispatch": "reduced"
19+
},
20+
"source_policy_decision_ref": "policy-decision:agent-alpha-review-001",
21+
"source_runtime_effect_decision_ref": null,
22+
"restoration_allowed": true,
23+
"restoration_conditions": ["fresh policy pass", "grant not expired", "no revocation ref present"],
24+
"evidence_refs": ["evidence://sourceos/grant-state/agent-alpha-review-001"],
25+
"explanation": "Policy review requires reduced authority; grant state change is recorded separately from policy and runtime effect decisions."
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"schema_version": "1.1.0",
3+
"decision_kind": "runtime-effect-decision",
4+
"decision_id": "runtime-effect-decision:invalid-authority-mutation",
5+
"ts_decided": "2026-05-27T00:51:00Z",
6+
"policy_decision_ref": "policy-decision:demo-export-001",
7+
"event_ref": {
8+
"event_id": "sourceos-event:demo-001",
9+
"event_content_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
10+
},
11+
"runtime_effect": "allow_dispatch",
12+
"effect_status": "admitted",
13+
"effect_scope": {
14+
"component_ref": "agent-term://dispatch",
15+
"operation": "dispatch_agent",
16+
"side_effecting": true
17+
},
18+
"authority_mutation_performed": true,
19+
"ledger_write_performed": false,
20+
"grant_state_decision_ref": "grant-state-decision:invalid-inline-mutation",
21+
"ledger_record_ref": null,
22+
"downstream_refs": [],
23+
"evidence_refs": ["evidence://sourceos/runtime-effect/invalid-authority-mutation"],
24+
"explanation": "Invalid fixture: runtime effect decisions must not mutate authority inline."
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"schema_version": "1.1.0",
3+
"decision_kind": "runtime-effect-decision",
4+
"decision_id": "runtime-effect-decision:demo-dispatch-001",
5+
"ts_decided": "2026-05-27T00:50:00Z",
6+
"policy_decision_ref": "policy-decision:demo-export-001",
7+
"event_ref": {
8+
"event_id": "sourceos-event:demo-001",
9+
"event_content_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
10+
},
11+
"runtime_effect": "export_ref_only",
12+
"effect_status": "admitted",
13+
"effect_scope": {
14+
"component_ref": "sourceos-shell://receipt-export",
15+
"operation": "export_receipt_ref",
16+
"side_effecting": false
17+
},
18+
"authority_mutation_performed": false,
19+
"ledger_write_performed": false,
20+
"grant_state_decision_ref": null,
21+
"ledger_record_ref": null,
22+
"downstream_refs": ["grant-state-decision:optional-followup"],
23+
"evidence_refs": ["evidence://sourceos/runtime-effect/demo-001"],
24+
"explanation": "Policy decision allows ref-only export; runtime effect admits only the ref export and performs no authority mutation."
25+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://sourceos.dev/schemas/grant-state-decision.v1.1.json",
4+
"title": "SourceOS Grant State Decision v1.1",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"required": [
8+
"schema_version",
9+
"decision_kind",
10+
"decision_id",
11+
"ts_decided",
12+
"grant_ref",
13+
"subject_ref",
14+
"authority_decision",
15+
"decision_actor_ref",
16+
"authorization_policy_ref",
17+
"authorization_evidence_refs",
18+
"effective_at",
19+
"authority_effects",
20+
"source_policy_decision_ref",
21+
"restoration_allowed"
22+
],
23+
"properties": {
24+
"schema_version": {"const": "1.1.0"},
25+
"decision_kind": {"const": "grant-state-decision"},
26+
"decision_id": {"type": "string", "minLength": 1},
27+
"ts_decided": {"type": "string", "format": "date-time"},
28+
"grant_ref": {"type": "string", "minLength": 1},
29+
"subject_ref": {"type": "string", "minLength": 1},
30+
"authority_decision": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]},
31+
"decision_actor_ref": {"type": "string", "minLength": 1},
32+
"authorization_policy_ref": {"type": "string", "minLength": 1},
33+
"authorization_evidence_refs": {"type": "array", "minItems": 1, "items": {"type": "string"}, "uniqueItems": true},
34+
"effective_at": {"type": "string", "format": "date-time"},
35+
"authority_effects": {
36+
"type": "object",
37+
"additionalProperties": false,
38+
"required": ["tool_access", "memory_access", "event_write", "bridge_export", "runtime_dispatch"],
39+
"properties": {
40+
"tool_access": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]},
41+
"memory_access": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]},
42+
"event_write": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]},
43+
"bridge_export": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]},
44+
"runtime_dispatch": {"type": "string", "enum": ["unchanged", "reduced", "suspended", "revoked", "restored"]}
45+
}
46+
},
47+
"source_policy_decision_ref": {"type": ["string", "null"]},
48+
"source_runtime_effect_decision_ref": {"type": ["string", "null"]},
49+
"restoration_allowed": {"type": "boolean"},
50+
"restoration_conditions": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
51+
"evidence_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
52+
"explanation": {"type": "string"}
53+
}
54+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://sourceos.dev/schemas/runtime-effect-decision.v1.1.json",
4+
"title": "SourceOS Runtime Effect Decision v1.1",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"required": [
8+
"schema_version",
9+
"decision_kind",
10+
"decision_id",
11+
"ts_decided",
12+
"policy_decision_ref",
13+
"event_ref",
14+
"runtime_effect",
15+
"effect_status",
16+
"effect_scope",
17+
"authority_mutation_performed",
18+
"ledger_write_performed",
19+
"evidence_refs"
20+
],
21+
"properties": {
22+
"schema_version": {"const": "1.1.0"},
23+
"decision_kind": {"const": "runtime-effect-decision"},
24+
"decision_id": {"type": "string", "minLength": 1},
25+
"ts_decided": {"type": "string", "format": "date-time"},
26+
"policy_decision_ref": {"type": "string", "minLength": 1},
27+
"event_ref": {
28+
"type": "object",
29+
"additionalProperties": false,
30+
"required": ["event_id", "event_content_sha256"],
31+
"properties": {
32+
"event_id": {"type": "string", "minLength": 1},
33+
"event_content_sha256": {"$ref": "#/$defs/sha256"}
34+
}
35+
},
36+
"runtime_effect": {
37+
"type": "string",
38+
"enum": ["allow_dispatch", "deny_dispatch", "require_review", "redact_payload", "metadata_only", "export_ref_only", "quarantine", "block", "noop"]
39+
},
40+
"effect_status": {"type": "string", "enum": ["admitted", "partial", "rejected", "requires_review", "failed_closed"]},
41+
"effect_scope": {
42+
"type": "object",
43+
"additionalProperties": false,
44+
"required": ["component_ref", "operation", "side_effecting"],
45+
"properties": {
46+
"component_ref": {"type": "string", "minLength": 1},
47+
"operation": {"type": "string", "minLength": 1},
48+
"side_effecting": {"type": "boolean"}
49+
}
50+
},
51+
"authority_mutation_performed": {"const": false},
52+
"ledger_write_performed": {"const": false},
53+
"grant_state_decision_ref": {"type": ["string", "null"]},
54+
"ledger_record_ref": {"type": ["string", "null"]},
55+
"downstream_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
56+
"evidence_refs": {"type": "array", "minItems": 1, "items": {"type": "string"}, "uniqueItems": true},
57+
"explanation": {"type": "string"}
58+
},
59+
"$defs": {"sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}}
60+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import json
5+
from pathlib import Path
6+
from typing import Any
7+
8+
import jsonschema
9+
10+
ROOT = Path(__file__).resolve().parents[1]
11+
RUNTIME_SCHEMA = ROOT / "schemas" / "runtime-effect-decision.v1.1.json"
12+
GRANT_SCHEMA = ROOT / "schemas" / "grant-state-decision.v1.1.json"
13+
RUNTIME_VALID = ROOT / "examples" / "runtime-effect-decision.valid.json"
14+
RUNTIME_INVALID_AUTHORITY = ROOT / "examples" / "runtime-effect-decision.authority-mutated.invalid.json"
15+
GRANT_VALID = ROOT / "examples" / "grant-state-decision.valid.json"
16+
GRANT_INVALID_MISSING_AUTH = ROOT / "examples" / "grant-state-decision.missing-authorization.invalid.json"
17+
18+
19+
class ValidationError(Exception):
20+
pass
21+
22+
23+
def load(path: Path) -> dict[str, Any]:
24+
payload = json.loads(path.read_text(encoding="utf-8"))
25+
if not isinstance(payload, dict):
26+
raise ValidationError(f"{path}: expected JSON object")
27+
return payload
28+
29+
30+
def validate_json_schema(schema_path: Path, instance_path: Path) -> dict[str, Any]:
31+
schema = load(schema_path)
32+
jsonschema.validators.validator_for(schema).check_schema(schema)
33+
instance = load(instance_path)
34+
jsonschema.validate(instance, schema)
35+
return instance
36+
37+
38+
def validate_runtime_effect(instance: dict[str, Any]) -> None:
39+
if instance.get("decision_kind") != "runtime-effect-decision":
40+
raise ValidationError("runtime effect decision_kind mismatch")
41+
if instance.get("authority_mutation_performed") is not False:
42+
raise ValidationError("runtime effect decisions must not mutate authority")
43+
if instance.get("ledger_write_performed") is not False:
44+
raise ValidationError("runtime effect decisions must not write ledger records")
45+
effect = instance.get("runtime_effect")
46+
status = instance.get("effect_status")
47+
scope = instance.get("effect_scope", {})
48+
if effect in {"allow_dispatch", "export_ref_only"} and status not in {"admitted", "partial"}:
49+
raise ValidationError(f"{effect} requires admitted or partial status")
50+
if effect in {"block", "quarantine", "deny_dispatch"} and status == "admitted":
51+
raise ValidationError(f"{effect} cannot report admitted status")
52+
if scope.get("side_effecting") is True and effect in {"metadata_only", "export_ref_only", "noop"}:
53+
raise ValidationError("metadata/ref/noop runtime effects cannot be side-effecting")
54+
if instance.get("grant_state_decision_ref") and instance.get("authority_mutation_performed") is not False:
55+
raise ValidationError("grant_state_decision_ref is a reference, not inline authority mutation")
56+
57+
58+
def validate_grant_state(instance: dict[str, Any]) -> None:
59+
if instance.get("decision_kind") != "grant-state-decision":
60+
raise ValidationError("grant state decision_kind mismatch")
61+
if not instance.get("authorization_policy_ref"):
62+
raise ValidationError("grant state decisions require authorization_policy_ref")
63+
if not instance.get("authorization_evidence_refs"):
64+
raise ValidationError("grant state decisions require authorization_evidence_refs")
65+
decision = instance.get("authority_decision")
66+
effects = instance.get("authority_effects", {})
67+
changed = any(value != "unchanged" for value in effects.values())
68+
if decision == "unchanged" and changed:
69+
raise ValidationError("unchanged grant state decision requires unchanged authority_effects")
70+
if decision != "unchanged" and not changed:
71+
raise ValidationError("changed grant state decision requires changed authority_effects")
72+
if decision == "revoked" and any(value != "revoked" for value in effects.values()):
73+
raise ValidationError("revoked grant state decision requires all authority_effects revoked")
74+
if decision == "restored" and instance.get("restoration_allowed") is not True:
75+
raise ValidationError("restored grant state decision requires restoration_allowed=true")
76+
77+
78+
def expect_invalid(schema_path: Path, instance_path: Path, semantic_validator) -> None:
79+
try:
80+
instance = validate_json_schema(schema_path, instance_path)
81+
semantic_validator(instance)
82+
except Exception:
83+
return
84+
raise ValidationError(f"invalid fixture unexpectedly validated: {instance_path.relative_to(ROOT)}")
85+
86+
87+
def main() -> int:
88+
runtime = validate_json_schema(RUNTIME_SCHEMA, RUNTIME_VALID)
89+
validate_runtime_effect(runtime)
90+
grant = validate_json_schema(GRANT_SCHEMA, GRANT_VALID)
91+
validate_grant_state(grant)
92+
expect_invalid(RUNTIME_SCHEMA, RUNTIME_INVALID_AUTHORITY, validate_runtime_effect)
93+
expect_invalid(GRANT_SCHEMA, GRANT_INVALID_MISSING_AUTH, validate_grant_state)
94+
print(json.dumps({"ok": True, "checks": [
95+
str(RUNTIME_VALID.relative_to(ROOT)),
96+
str(GRANT_VALID.relative_to(ROOT)),
97+
str(RUNTIME_INVALID_AUTHORITY.relative_to(ROOT)),
98+
str(GRANT_INVALID_MISSING_AUTH.relative_to(ROOT)),
99+
]}, indent=2, sort_keys=True))
100+
return 0
101+
102+
103+
if __name__ == "__main__":
104+
raise SystemExit(main())

0 commit comments

Comments
 (0)