Skip to content
Open
27 changes: 6 additions & 21 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
.PHONY: validate validate-json validate-schemas validate-control-plane validate-eventctl validate-event-store validate-events validate-identity validate-process-provenance validate-service-graph install-dev
.PHONY: validate validate-json validate-schemas validate-control-plane validate-eventctl validate-event-store validate-events validate-identity validate-process-provenance validate-service-graph validate-semantic-enterprise-state-integrity install-dev

validate: validate-json validate-schemas validate-control-plane validate-eventctl validate-event-store validate-events validate-identity validate-process-provenance validate-service-graph
validate: validate-json validate-schemas validate-control-plane validate-eventctl validate-event-store validate-events validate-identity validate-process-provenance validate-service-graph validate-semantic-enterprise-state-integrity

install-dev:
python3 -m pip install -r requirements-dev.txt

validate-json:
python3 - <<'PY'
import json
import pathlib
import sys

failed = False
for root in (pathlib.Path('schemas'), pathlib.Path('examples')):
if not root.exists():
continue
for path in sorted(root.rglob('*.json')):
try:
json.loads(path.read_text())
except Exception as exc:
print(f'{path}: invalid JSON: {exc}', file=sys.stderr)
failed = True
if failed:
raise SystemExit(1)
print('JSON syntax validated.')
PY
python3 tools/validate_json_syntax.py

validate-schemas:
python3 tools/validate_json_schemas.py
Expand Down Expand Up @@ -63,3 +45,6 @@ validate-process-provenance:
validate-service-graph:
python3 tools/sourceos_service_graph.py validate examples/services/*.json
python3 tools/sourceos_service_graph.py graph examples/services/*.json --json >/dev/null

validate-semantic-enterprise-state-integrity:
python3 tools/validate_semantic_enterprise_state_integrity.py
68 changes: 68 additions & 0 deletions docs/semantic-enterprise-state-integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Semantic Enterprise State Integrity Mapping v0.1

`sourceos-syncd` consumes `semantic-enterprise-v0.1.0` from `SocioProphet/ontogenesis` as a state-integrity mapping surface.

The local fixture is:

- `examples/semantic-enterprise/v0.1/state-integrity-mapping.example.json`

The validator is:

- `tools/validate_semantic_enterprise_state_integrity.py`

## Source release

- Repository: `SocioProphet/ontogenesis`
- Release/tag: `semantic-enterprise-v0.1.0`
- Manifest: `manifests/semantic_enterprise_v0_1_manifest.json`
- Rollup registry: `catalog/semantic_enterprise_v0_1_registry.ttl`
- Supply-chain module: `Domains/supply-chain.ttl`
- Named graph fixture: `examples/named-graphs/semantic_sector_named_graphs.ttl`

## State integrity surfaces

The v0.1 mapping covers:

- artifact lineage
- release provenance
- repair lineage
- rollback evidence
- local-first state context
- named graph governance

## Semantic bindings

The fixture maps SourceOS state surfaces to Semantic Enterprise concepts:

- `release_artifact` -> `supply-chain:Component`
- `state_integrity_report` -> `named-graph-governance:CuratedGraph`
- `repair_plan` -> `supply-chain:MitigationAction`
- `rollback_evidence` -> `supply-chain:AlternateSource`

## Closure boundary

The mapping distinguishes:

- `inside_source`: Ontogenesis authors semantic source modules and supply-chain scenarios.
- `outside_state_runtime`: SourceOS syncd maps semantic provenance into local-first state integrity evidence.
- `boundary_membrane`: release tag, source path, graph URI, trust level, access class, retention policy, and lifecycle phase survive translation.
- `feedback_surface`: SourceOS repair, rollback, and state reports remain downstream evidence.

## Validation

Run:

```bash
make validate
```

or:

```bash
python3 tools/validate_semantic_enterprise_state_integrity.py
```

## Parent work

- `SourceOS-Linux/sourceos-syncd#17`
- `SocioProphet/delivery-excellence#21`
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"contract": "sourceos-syncd.semantic-enterprise.state-integrity",
"version": "0.1.0",
"source": {
"repository": "SocioProphet/ontogenesis",
"release": "semantic-enterprise-v0.1.0",
"manifest_path": "manifests/semantic_enterprise_v0_1_manifest.json",
"rollup_registry_path": "catalog/semantic_enterprise_v0_1_registry.ttl",
"supply_chain_module_path": "Domains/supply-chain.ttl",
"named_graph_fixture_path": "examples/named-graphs/semantic_sector_named_graphs.ttl"
},
"state_integrity_surfaces": [
"artifact_lineage",
"release_provenance",
"repair_lineage",
"rollback_evidence",
"local_first_state_context",
"named_graph_governance"
],
"provenance_requirements": [
"source_path",
"graph_uri",
"source_system",
"trust_level",
"access_class",
"retention_policy",
"lifecycle_phase",
"release_tag"
],
"semantic_bindings": [
{
"sourceos_surface": "release_artifact",
"semantic_enterprise_class": "supply-chain:Component",
"evidence_role": "software-artifact-component",
"required_fields": ["artifact_id", "source_path", "release_tag", "trust_level"]
},
{
"sourceos_surface": "state_integrity_report",
"semantic_enterprise_class": "named-graph-governance:CuratedGraph",
"evidence_role": "curated-state-perspective",
"required_fields": ["graph_uri", "source_system", "access_class", "retention_policy"]
},
{
"sourceos_surface": "repair_plan",
"semantic_enterprise_class": "supply-chain:MitigationAction",
"evidence_role": "non-destructive-repair-mitigation",
"required_fields": ["plan_id", "source_path", "affected_component", "approval_state"]
},
{
"sourceos_surface": "rollback_evidence",
"semantic_enterprise_class": "supply-chain:AlternateSource",
"evidence_role": "alternate-source-or-rollback-reference",
"required_fields": ["rollback_id", "source_path", "previous_state_ref", "trust_level"]
}
],
"named_graph_contexts": [
{
"sector": "supply-chain",
"source_path": "examples/scenarios/supply_chain_resilience_demo.ttl",
"graph_uri_fragment": "graphs/scenarios/supply-chain-resilience",
"source_system": "Ontogenesis semantic-enterprise scenario fixture",
"access_class": "internal",
"trust_level": "curated-demo",
"retention_policy": "retain-current-plus-audit-history",
"lifecycle_phase": "KnowledgeCuration"
}
],
"sourceos_fixture": {
"artifact_id": "sourceos-demo-release-component",
"release_tag": "semantic-enterprise-v0.1.0",
"state_context": "local-first-state-integrity-demo",
"graph_uri_fragment": "graphs/scenarios/supply-chain-resilience",
"trust_level": "curated-demo",
"access_class": "internal",
"operator_narrative": "Semantic Enterprise supply-chain provenance is mapped into SourceOS state integrity evidence without mutating Ontogenesis source semantics."
},
"closure_model": {
"inside_source": "Ontogenesis authors the semantic source modules and supply-chain scenario.",
"outside_state_runtime": "SourceOS syncd maps semantic provenance into local-first state integrity evidence.",
"boundary_membrane": "Release tag, source path, graph URI, trust level, access class, retention policy, and lifecycle phase must survive translation.",
"feedback_surface": "SourceOS repair, rollback, and state reports remain downstream evidence and do not mutate Ontogenesis source semantics."
}
}
16 changes: 8 additions & 8 deletions src/sourceos_syncd/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ class JsonContract:
"""Base class for JSON-serializable contracts."""

schema: ClassVar[str]
required_fields: ClassVar[set[str]] = set()
controlled_fields: ClassVar[dict[str, set[str]]] = {}
list_fields: ClassVar[set[str]] = set()
mapping_fields: ClassVar[set[str]] = set()
required_fields: ClassVar[set[str]]
controlled_fields: ClassVar[dict[str, set[str]]]
list_fields: ClassVar[set[str]]
mapping_fields: ClassVar[set[str]]

def to_dict(self) -> dict[str, Any]:
data = dataclasses.asdict(self)
Expand All @@ -79,12 +79,12 @@ def to_dict(self) -> dict[str, Any]:
def validate_dict(cls, record: dict[str, Any]) -> dict[str, Any]:
if not isinstance(record, dict):
raise ContractError(f"{cls.__name__} must be a JSON object")
require_fields(record, cls.required_fields, cls.__name__)
for field_name, allowed in cls.controlled_fields.items():
require_fields(record, getattr(cls, "required_fields", set()), cls.__name__)
for field_name, allowed in getattr(cls, "controlled_fields", {}).items():
require_allowed(str(record.get(field_name)), allowed, field_name, cls.__name__)
for field_name in cls.list_fields:
for field_name in getattr(cls, "list_fields", set()):
require_list(record, field_name, cls.__name__)
for field_name in cls.mapping_fields:
for field_name in getattr(cls, "mapping_fields", set()):
require_mapping(record, field_name, cls.__name__)
return record

Expand Down
32 changes: 32 additions & 0 deletions tools/validate_json_syntax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""Validate JSON syntax for repository schemas and examples."""
from __future__ import annotations

import json
from pathlib import Path
import sys

ROOT = Path(__file__).resolve().parents[1]


def main() -> int:
failed = False
for root_name in ("schemas", "examples"):
root = ROOT / root_name
if not root.exists():
continue
for path in sorted(root.rglob("*.json")):
try:
json.loads(path.read_text(encoding="utf-8"))
except Exception as exc: # noqa: BLE001 - syntax validator should report any parse/read failure.
rel = path.relative_to(ROOT)
print(f"{rel}: invalid JSON: {exc}", file=sys.stderr)
failed = True
if failed:
return 1
print("JSON syntax validated.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
85 changes: 81 additions & 4 deletions tools/validate_orchestration_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@
OUTCOMES = {"allowed", "denied", "requires_approval", "requires_local_only", "redacted", "degraded"}


def load_json(path: pathlib.Path) -> dict[str, Any]:
def load_json(path: pathlib.Path) -> Any:
try:
data = json.loads(path.read_text())
return json.loads(path.read_text())
except Exception as exc:
raise SystemExit(f"{path}: invalid JSON: {exc}") from exc


def load_json_object(path: pathlib.Path) -> dict[str, Any]:
data = load_json(path)
if not isinstance(data, dict):
raise SystemExit(f"{path}: expected top-level JSON object")
return data
Expand All @@ -49,7 +53,7 @@ def collect_ids(items: list[dict[str, Any]], field: str, path: pathlib.Path, err


def validate_bundle(path: pathlib.Path) -> list[str]:
obj = load_json(path)
obj = load_json_object(path)
errors: list[str] = []

missing = REQUIRED_TOP_LEVEL - set(obj)
Expand Down Expand Up @@ -123,10 +127,83 @@ def validate_bundle(path: pathlib.Path) -> list[str]:
return errors


def validate_event_capability_records(path: pathlib.Path) -> list[str]:
data = load_json(path)
errors: list[str] = []
if not isinstance(data, list):
return [f"{path}: expected top-level JSON array for *.records.json"]
if not data:
return [f"{path}: record set must not be empty"]

record_ids: set[str] = set()
for index, record in enumerate(data):
if not isinstance(record, dict):
errors.append(f"{path}: record {index} must be an object")
continue
record_id = record.get("record_id")
if not isinstance(record_id, str) or not record_id:
errors.append(f"{path}: record {index} missing record_id")
elif record_id in record_ids:
errors.append(f"{path}: duplicate record_id {record_id}")
else:
record_ids.add(record_id)

if record.get("mode") != "event-capability-evidence-v0":
errors.append(f"{path}: record {record_id} mode must be event-capability-evidence-v0")

event = record.get("event")
capability = record.get("capability")
reaction = record.get("reaction")
if not isinstance(event, dict):
errors.append(f"{path}: record {record_id} missing event object")
continue
if not isinstance(capability, dict):
errors.append(f"{path}: record {record_id} missing capability object")
continue
if not isinstance(reaction, dict):
errors.append(f"{path}: record {record_id} missing reaction object")
continue

for key in ("event_id", "event_type", "target_node_id"):
if not event.get(key):
errors.append(f"{path}: record {record_id} event missing {key}")
causality = event.get("causality") or {}
for key in ("idempotency_key", "policy_epoch"):
if not causality.get(key):
errors.append(f"{path}: record {record_id} event.causality missing {key}")

for key in ("capability_id", "display_name", "effect_class", "required_policy_outcome", "approval_mode"):
if not capability.get(key):
errors.append(f"{path}: record {record_id} capability missing {key}")
if capability.get("required_policy_outcome") not in OUTCOMES:
errors.append(f"{path}: record {record_id} capability has invalid required_policy_outcome")

for key in ("reaction_id", "event_id", "capability_id", "policy_outcome", "status"):
if not reaction.get(key):
errors.append(f"{path}: record {record_id} reaction missing {key}")
if reaction.get("event_id") != event.get("event_id"):
errors.append(f"{path}: record {record_id} reaction event_id must match event.event_id")
if reaction.get("capability_id") != capability.get("capability_id"):
errors.append(f"{path}: record {record_id} reaction capability_id must match capability.capability_id")
if reaction.get("policy_outcome") not in OUTCOMES:
errors.append(f"{path}: record {record_id} reaction has invalid policy_outcome")
if not isinstance(reaction.get("receipt_refs"), list) or not reaction.get("receipt_refs"):
errors.append(f"{path}: record {record_id} reaction must include receipt_refs")
if not isinstance(reaction.get("dead_letter_on_failure"), bool):
errors.append(f"{path}: record {record_id} reaction.dead_letter_on_failure must be boolean")
if not isinstance(record.get("evidence_refs"), list) or not record.get("evidence_refs"):
errors.append(f"{path}: record {record_id} must include evidence_refs")

return errors


def main() -> int:
errors: list[str] = []
for path in sorted(BUNDLE_DIR.glob("*.json")):
errors.extend(validate_bundle(path))
if path.name.endswith(".records.json"):
errors.extend(validate_event_capability_records(path))
else:
errors.extend(validate_bundle(path))

if errors:
for error in errors:
Expand Down
Loading
Loading