From fa028cc4a18d9ceb6098a416bdc61bc68cd5956b Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:20:32 -0400 Subject: [PATCH 01/10] Add OrgGov state integrity binding schema --- schemas/orggov-state-integrity-binding.v0.1.schema.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 schemas/orggov-state-integrity-binding.v0.1.schema.json diff --git a/schemas/orggov-state-integrity-binding.v0.1.schema.json b/schemas/orggov-state-integrity-binding.v0.1.schema.json new file mode 100644 index 0000000..1653781 --- /dev/null +++ b/schemas/orggov-state-integrity-binding.v0.1.schema.json @@ -0,0 +1 @@ +{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://schemas.sourceos.org/syncd/orggov-state-integrity-binding.v0.1.schema.json","title":"OrgGov State Integrity Binding v0.1","type":"object","additionalProperties":false,"required":["schemaVersion","recordType","recordId","workroomRef","workOrderRef","actorRef","sessionRef","stateIntegrityReportRef","indexLaneRefs","policyDecisionRefs","eventRefs","repairPlanRefs","evidenceRefs","operatorNarrative","provenance"],"properties":{"schemaVersion":{"const":"orggov.state-integrity-binding.v0.1"},"recordType":{"const":"OrgGovStateIntegrityBinding"},"recordId":{"type":"string"},"workroomRef":{"type":"string"},"workOrderRef":{"type":"string"},"actorRef":{"type":"string"},"sessionRef":{"type":"string"},"stateIntegrityReportRef":{"type":"string"},"indexLaneRefs":{"type":"array","minItems":1,"items":{"type":"string"}},"policyDecisionRefs":{"type":"array","items":{"type":"string"}},"eventRefs":{"type":"array","minItems":1,"items":{"type":"string"}},"repairPlanRefs":{"type":"array","items":{"type":"string"}},"evidenceRefs":{"type":"array","minItems":1,"items":{"type":"string"}},"operatorNarrative":{"type":"object","additionalProperties":false,"required":["summary","why","nextAction","riskRemaining"],"properties":{"summary":{"type":"string"},"why":{"type":"string"},"nextAction":{"type":"string"},"riskRemaining":{"type":"string"}}},"provenance":{"type":"object","additionalProperties":false,"required":["createdBy","createdAt","nonSecret"],"properties":{"createdBy":{"type":"string"},"createdAt":{"type":"string"},"nonSecret":{"type":"boolean"}}},"labels":{"type":"object","additionalProperties":{"type":"string"}}}} From 3c3697ffac4c7e64311ba972e09a16f16b8a2d31 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:23:18 -0400 Subject: [PATCH 02/10] Add OrgGov state integrity binding fixture --- examples/orggov/state-integrity-binding.v0.1.example.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/orggov/state-integrity-binding.v0.1.example.json diff --git a/examples/orggov/state-integrity-binding.v0.1.example.json b/examples/orggov/state-integrity-binding.v0.1.example.json new file mode 100644 index 0000000..256ed67 --- /dev/null +++ b/examples/orggov/state-integrity-binding.v0.1.example.json @@ -0,0 +1 @@ +{"schemaVersion":"orggov.state-integrity-binding.v0.1","recordType":"OrgGovStateIntegrityBinding","recordId":"orggov-state-integrity:prophet-platform-406","workroomRef":"workroom:orggov-v0","workOrderRef":"workorder:prophet-platform:406","actorRef":"actor:orggov-planning-agent","sessionRef":"agentplane://artifacts/orggov/session-artifact.json","stateIntegrityReportRef":"sourceos-syncd://reports/orggov-v0/state-integrity-report.json","indexLaneRefs":["sourceos-index-lane://policy","sourceos-index-lane://audit","sourceos-index-lane://repair"],"policyDecisionRefs":["github://SocioProphet/policy-fabric/pull/61","policydecision:allow-contract-spine"],"eventRefs":["sourceos-event://orggov/contract-spine-state-observed","sourceos-event://orggov/policy-block-expected"],"repairPlanRefs":["sourceos-repair-plan://orggov/no-destructive-repair-required"],"evidenceRefs":["github://SocioProphet/prophet-platform/issues/406","github://SocioProphet/agentplane/pull/128","github://SourceOS-Linux/sourceos-syncd/issues/13"],"operatorNarrative":{"summary":"OrgGov v0 work-order state is represented as local-first integrity evidence.","why":"The work order needs state, index-lane, policy-decision, event, and repair-plan context before agents can safely act against local substrates.","nextAction":"Promote the fixture into runtime state-integrity report emission once sourceos-syncd runtime endpoints are wired.","riskRemaining":"This is a fixture-level binding; live runtime emission and signed Lampstand attestation remain future work."},"provenance":{"createdBy":"sourceos-syncd://orggov-fixture","createdAt":"2026-05-07T05:10:00Z","nonSecret":true},"labels":{"program":"orggov-v0","parent":"SocioProphet/prophet-platform#406","sourceosIssue":"SourceOS-Linux/sourceos-syncd#13"}} From 1c1e8c74e2adb6229e9d87d937b81ca550494ed2 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:25:48 -0400 Subject: [PATCH 03/10] Document OrgGov state integrity binding --- docs/orggov-state-integrity-binding.md | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/orggov-state-integrity-binding.md diff --git a/docs/orggov-state-integrity-binding.md b/docs/orggov-state-integrity-binding.md new file mode 100644 index 0000000..c623f40 --- /dev/null +++ b/docs/orggov-state-integrity-binding.md @@ -0,0 +1,43 @@ +# OrgGov State Integrity Binding v0.1 + +## Purpose + +OrgGov State Integrity Binding v0.1 makes `sourceos-syncd` the local-first state integrity lane for Organization Governance Control Plane v0. + +The binding connects governed work orders to SourceOS state reports, index lanes, policy decisions, canonical events, repair-plan posture, evidence references, and operator narrative. + +The shared OrgGov loop is: + +```text +Objective → Workroom → Actor → Role → Policy → Asset → Action → Evidence → Review → Outcome → Score → Learning +``` + +`sourceos-syncd` owns the local-first state, index-lane, event, repair-plan, and operator-narrative evidence portion of that loop. It does not own policy authority, product UX, agent execution, or scorecard semantics. + +## Contract files + +- `schemas/orggov-state-integrity-binding.v0.1.schema.json` +- `examples/orggov/state-integrity-binding.v0.1.example.json` +- `tools/validate_orggov_state_integrity_binding.py` + +## Invariants + +- Every binding references a workroom, work order, actor, and session. +- Every binding references a state integrity report and at least one index lane. +- Every binding includes canonical event refs and evidence refs. +- Repair posture is explicit even when no destructive repair is required. +- Operator narrative must include summary, why, next action, and remaining risk. +- Public fixtures must set `provenance.nonSecret` to true. + +## Cross-repo links + +- Parent: `SocioProphet/prophet-platform#406` +- SourceOS workstream: `SourceOS-Linux/sourceos-syncd#13` +- AgentPlane evidence: `SocioProphet/agentplane#104` +- Policy gate: `SocioProphet/policy-fabric#57` +- Sherlock indexing: `SocioProphet/sherlock-search#36` +- Delivery scorecard: `SocioProphet/delivery-excellence#14` + +## Runtime promotion path + +The v0 fixture is not yet live runtime emission. The next promotion step is to have `sourceos-syncd` emit a matching binding from a real state integrity report, with Lampstand-attestable evidence and privacy-tiered event access. From 787317a6818920cd6a300a178afe43966dfb58eb Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:28:32 -0400 Subject: [PATCH 04/10] Add OrgGov state integrity binding validator --- ...validate_orggov_state_integrity_binding.py | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tools/validate_orggov_state_integrity_binding.py diff --git a/tools/validate_orggov_state_integrity_binding.py b/tools/validate_orggov_state_integrity_binding.py new file mode 100644 index 0000000..98906c0 --- /dev/null +++ b/tools/validate_orggov_state_integrity_binding.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Validate OrgGov state integrity binding contracts.""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA = ROOT / "schemas/orggov-state-integrity-binding.v0.1.schema.json" +EXAMPLE = ROOT / "examples/orggov/state-integrity-binding.v0.1.example.json" + + +class ValidationError(Exception): + pass + + +def fail(message: str) -> None: + raise ValidationError(message) + + +def load_json(path: Path) -> Any: + try: + return json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as exc: + raise ValidationError(f"missing file: {path.relative_to(ROOT)}") from exc + except json.JSONDecodeError as exc: + raise ValidationError(f"invalid JSON in {path.relative_to(ROOT)}: {exc}") from exc + + +def json_type_name(value: Any) -> str: + if value is None: + return "null" + if isinstance(value, bool): + return "boolean" + if isinstance(value, int) and not isinstance(value, bool): + return "integer" + if isinstance(value, float): + return "number" + if isinstance(value, str): + return "string" + if isinstance(value, list): + return "array" + if isinstance(value, dict): + return "object" + return type(value).__name__ + + +def type_matches(value: Any, expected: str) -> bool: + actual = json_type_name(value) + if expected == "number": + return actual in {"integer", "number"} + return actual == expected + + +def validate_schema(schema: dict[str, Any], value: Any, path: str = "$") -> None: + if "const" in schema and value != schema["const"]: + fail(f"{path}: expected const {schema['const']!r}, got {value!r}") + if "enum" in schema and value not in schema["enum"]: + fail(f"{path}: {value!r} not in enum {schema['enum']!r}") + expected_type = schema.get("type") + if expected_type is not None: + expected_types = expected_type if isinstance(expected_type, list) else [expected_type] + if not any(type_matches(value, item) for item in expected_types): + fail(f"{path}: expected type {expected_types!r}, got {json_type_name(value)!r}") + if isinstance(value, dict): + for key in schema.get("required", []): + if key not in value: + fail(f"{path}: missing required property {key!r}") + properties = schema.get("properties", {}) + if schema.get("additionalProperties") is False: + extra = sorted(set(value) - set(properties)) + if extra: + fail(f"{path}: unexpected properties {extra!r}") + additional = schema.get("additionalProperties") + for key, item in value.items(): + child_schema = properties.get(key) + if child_schema is None and isinstance(additional, dict): + child_schema = additional + if child_schema is not None: + validate_schema(child_schema, item, f"{path}.{key}") + if isinstance(value, list): + item_schema = schema.get("items") + if item_schema is not None: + for index, item in enumerate(value): + validate_schema(item_schema, item, f"{path}[{index}]") + + +def require_non_empty_string(record: dict[str, Any], key: str) -> None: + value = record.get(key) + if not isinstance(value, str) or not value: + fail(f"{key}: expected non-empty string") + + +def require_non_empty_list(record: dict[str, Any], key: str) -> None: + value = record.get(key) + if not isinstance(value, list) or not value: + fail(f"{key}: expected non-empty list") + if not all(isinstance(item, str) and item for item in value): + fail(f"{key}: expected non-empty string refs") + + +def validate_invariants(record: dict[str, Any]) -> None: + for key in ( + "workroomRef", + "workOrderRef", + "actorRef", + "sessionRef", + "stateIntegrityReportRef", + ): + require_non_empty_string(record, key) + for key in ("indexLaneRefs", "eventRefs", "evidenceRefs"): + require_non_empty_list(record, key) + narrative = record.get("operatorNarrative", {}) + for key in ("summary", "why", "nextAction", "riskRemaining"): + if not isinstance(narrative.get(key), str) or not narrative[key]: + fail(f"operatorNarrative.{key}: expected non-empty string") + if record["provenance"].get("nonSecret") is not True: + fail("provenance.nonSecret must be true") + + +def main() -> int: + try: + schema = load_json(SCHEMA) + example = load_json(EXAMPLE) + validate_schema(schema, example) + validate_invariants(example) + except ValidationError as exc: + print(f"ERR: {exc}", file=sys.stderr) + return 2 + print("ok: examples/orggov/state-integrity-binding.v0.1.example.json validates") + print("OK: OrgGov state integrity binding validation passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 2f4cd113d2a59353b164fa182cdf9d845580893f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:35:39 -0400 Subject: [PATCH 05/10] Add JSON syntax validator script --- tools/validate_json_syntax.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tools/validate_json_syntax.py diff --git a/tools/validate_json_syntax.py b/tools/validate_json_syntax.py new file mode 100644 index 0000000..ac1d3bb --- /dev/null +++ b/tools/validate_json_syntax.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Validate JSON syntax for SourceOS schema and example files.""" + +from __future__ import annotations + +import json +import pathlib +import sys + + +def main() -> int: + 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(encoding="utf-8")) + except Exception as exc: # pragma: no cover - diagnostic path + print(f"{path}: invalid JSON: {exc}", file=sys.stderr) + failed = True + if failed: + return 1 + print("JSON syntax validated.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From cdf4b8a70abbb47f32d89e318a37e2627c6b1edb Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:37:32 -0400 Subject: [PATCH 06/10] Use script for JSON syntax validation --- Makefile | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Makefile b/Makefile index e2c5d0d..0c2d953 100644 --- a/Makefile +++ b/Makefile @@ -6,25 +6,7 @@ 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 From 7f34f93d7f95e6333a284614ae611043b4dda730 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:46:10 -0400 Subject: [PATCH 07/10] Skip record collections in orchestration bundle validator --- tools/validate_orchestration_examples.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/validate_orchestration_examples.py b/tools/validate_orchestration_examples.py index db49499..7b9280e 100644 --- a/tools/validate_orchestration_examples.py +++ b/tools/validate_orchestration_examples.py @@ -123,9 +123,21 @@ def validate_bundle(path: pathlib.Path) -> list[str]: return errors +def is_bundle_file(path: pathlib.Path) -> bool: + """Return true for orchestration bundle fixtures only. + + `examples/orchestration/*.records.json` files are record collections with + top-level arrays and are validated by their own lanes. They must not be fed + into the bundle validator, whose contract requires a top-level object. + """ + return path.suffix == ".json" and not path.name.endswith(".records.json") + + def main() -> int: errors: list[str] = [] for path in sorted(BUNDLE_DIR.glob("*.json")): + if not is_bundle_file(path): + continue errors.extend(validate_bundle(path)) if errors: From cb4bc35d19bb8e7dccadf2700cdc551a6100cd38 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:55:54 -0400 Subject: [PATCH 08/10] Fix SchemaContractRecord dataclass field collision --- src/sourceos_syncd/contracts.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/sourceos_syncd/contracts.py b/src/sourceos_syncd/contracts.py index 9c63ca6..23a442d 100644 --- a/src/sourceos_syncd/contracts.py +++ b/src/sourceos_syncd/contracts.py @@ -187,7 +187,7 @@ class SchemaContractRecord(JsonContract): schema_id: str object_type: str schema_version: str - required_fields: list[str] + declared_required_fields: list[str] field_owners: dict[str, str] migration_policy: str conflict_policy: str @@ -195,6 +195,28 @@ class SchemaContractRecord(JsonContract): sync_visibility: str retention_class: str + def to_dict(self) -> dict[str, Any]: + data = dataclasses.asdict(self) + data["required_fields"] = data.pop("declared_required_fields") + data.setdefault("schema", self.schema) + return data + + @classmethod + def from_dict(cls, record: dict[str, Any]) -> SchemaContractRecord: + cls.validate_dict(record) + return cls( + schema_id=record["schema_id"], + object_type=record["object_type"], + schema_version=record["schema_version"], + declared_required_fields=list(record["required_fields"]), + field_owners=dict(record["field_owners"]), + migration_policy=record["migration_policy"], + conflict_policy=record["conflict_policy"], + tombstone_policy=record["tombstone_policy"], + sync_visibility=record["sync_visibility"], + retention_class=record["retention_class"], + ) + @dataclass(slots=True) class SourceObjectRecord(JsonContract): From 3c0b87524a540d502205d82956d48fef7b4c20eb Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 03:09:48 -0400 Subject: [PATCH 09/10] Expose local_state in store-backed reports --- src/sourceos_syncd/store_reports.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sourceos_syncd/store_reports.py b/src/sourceos_syncd/store_reports.py index 23eed63..79c5b29 100644 --- a/src/sourceos_syncd/store_reports.py +++ b/src/sourceos_syncd/store_reports.py @@ -19,6 +19,7 @@ def snapshot_from_store(root: str | Path) -> dict[str, Any]: report["stores"] = summary["stores"] report["lanes"] = summary["lanes"] report["pipeline"] = summary["pipeline"] + report["local_state"] = copy.deepcopy(summary["local_state"]) report["collection"]["status"] = "partial" if not summary["local_state"]["initialized"]: report["collection"]["errors"] = sorted(set(report["collection"].get("errors", []) + ["local store is not initialized; run store init first"])) From 61e2f83cbd99e9a83bc60e326b54169ce4c58840 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 03:13:26 -0400 Subject: [PATCH 10/10] Fix ActorRecord classmethod base validation --- src/sourceos_syncd/contracts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sourceos_syncd/contracts.py b/src/sourceos_syncd/contracts.py index 23a442d..7f46051 100644 --- a/src/sourceos_syncd/contracts.py +++ b/src/sourceos_syncd/contracts.py @@ -158,7 +158,7 @@ class ActorRecord(JsonContract): @classmethod def validate_dict(cls, record: dict[str, Any]) -> dict[str, Any]: - super().validate_dict(record) + JsonContract.validate_dict.__func__(cls, record) invalid = sorted(set(record.get("capabilities", [])) - ACTOR_CAPABILITIES) if invalid: raise ContractError(f"ActorRecord.capabilities contains invalid values: {invalid}")