From 031507f867488156eee72beb94d15bf3c8d6b4b7 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:58:15 -0400 Subject: [PATCH] feat(risk): supply-chain operational-risk contract (BIAN/FICO) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4 (final) of the 5 handoff specs — a bank-style operational-risk framework applied to the supply chain. Layers onto the supply-chain spine: nodes/paths/ clusters gain risk assessments. - RiskNode: inherent factors (criticality·privilege·execution·opacity· concentration·velocity) → inherent score; control efficacy across six families (governance·identity·provenance·integrity·distribution·monitoring) → residual score + rating. Aligns to BIAN Operational Risk Models. - RiskPath: accumulates node residuals across a critical service chain. - RiskCluster: common-mode / concentration (HHI, blast radius, time-to-recover, exit-difficulty → residual common-mode). - RiskIndicator: KRI/KCI with green/amber/red thresholds. + examples + tools/validate_supply_chain_risk_examples.py + Makefile target. All 4 validate. Completes the 5-spec program (agentic OS + triparty marketplace + labor market + risk). --- Makefile | 8 +- examples/risk_cluster.json | 18 ++ examples/risk_indicator.json | 15 ++ examples/risk_node.json | 30 ++++ examples/risk_path.json | 15 ++ schemas/RiskCluster.json | 90 ++++++++++ schemas/RiskIndicator.json | 95 ++++++++++ schemas/RiskNode.json | 176 +++++++++++++++++++ schemas/RiskPath.json | 73 ++++++++ tools/validate_supply_chain_risk_examples.py | 43 +++++ 10 files changed, 561 insertions(+), 2 deletions(-) create mode 100644 examples/risk_cluster.json create mode 100644 examples/risk_indicator.json create mode 100644 examples/risk_node.json create mode 100644 examples/risk_path.json create mode 100644 schemas/RiskCluster.json create mode 100644 schemas/RiskIndicator.json create mode 100644 schemas/RiskNode.json create mode 100644 schemas/RiskPath.json create mode 100644 tools/validate_supply_chain_risk_examples.py diff --git a/Makefile b/Makefile index f5570ba..6878a1d 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ -.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples +.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-supply-chain-risk-examples -validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples +validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-supply-chain-risk-examples @echo "OK: validate" +validate-supply-chain-risk-examples: + python3 -m pip install --user jsonschema >/dev/null + python3 tools/validate_supply_chain_risk_examples.py + validate-control-plane-examples: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_control_plane_examples.py diff --git a/examples/risk_cluster.json b/examples/risk_cluster.json new file mode 100644 index 0000000..f157924 --- /dev/null +++ b/examples/risk_cluster.json @@ -0,0 +1,18 @@ +{ + "id": "urn:srcos:risk-cluster:trans-pacific-shipping", + "type": "RiskCluster", + "specVersion": "2.0.0", + "clusterName": "Trans-Pacific shipping lanes", + "clusterType": "Logistics / route concentration", + "providerShares": [ + 0.55, + 0.25, + 0.2 + ], + "hhi": 0.39, + "blastRadius": 0.6, + "timeToRecover": 0.5, + "exitDifficulty": 0.7, + "residualCommonMode": 0.52, + "rating": "High" +} diff --git a/examples/risk_indicator.json b/examples/risk_indicator.json new file mode 100644 index 0000000..83c7b12 --- /dev/null +++ b/examples/risk_indicator.json @@ -0,0 +1,15 @@ +{ + "id": "urn:srcos:risk-indicator:tier0-provenance", + "type": "RiskIndicator", + "specVersion": "2.0.0", + "indicatorType": "KRI", + "metric": "Tier-0 nodes with verified provenance", + "definition": "Share of tier-0 supply-chain nodes with verified provenance attached before admission.", + "value": 0.82, + "green": ">=95%", + "amber": "80-94.9%", + "red": "<80%", + "band": "amber", + "frequency": "Weekly", + "owner": "Supply Chain Risk" +} diff --git a/examples/risk_node.json b/examples/risk_node.json new file mode 100644 index 0000000..62e63ca --- /dev/null +++ b/examples/risk_node.json @@ -0,0 +1,30 @@ +{ + "id": "urn:srcos:risk-node:escondida", + "type": "RiskNode", + "specVersion": "2.0.0", + "nodeRef": "hg:facility/escondida", + "criticalService": "Copper cathode supply", + "inherent": { + "criticality": 0.9, + "privilege": 0.4, + "execution": 0.2, + "opacity": 0.5, + "concentration": 0.8, + "velocity": 0.3 + }, + "inherentScore": 0.62, + "controls": { + "governance": 0.7, + "identityPublish": 0.5, + "provenance": 0.6, + "integrity": 0.6, + "distribution": 0.4, + "monitoring": 0.6 + }, + "residualScore": 0.34, + "rating": "Medium", + "controlGaps": [ + "distribution" + ], + "note": "World's largest copper mine — high criticality + concentration; water is the swing risk." +} diff --git a/examples/risk_path.json b/examples/risk_path.json new file mode 100644 index 0000000..4ab7457 --- /dev/null +++ b/examples/risk_path.json @@ -0,0 +1,15 @@ +{ + "id": "urn:srcos:risk-path:copper-cathode", + "type": "RiskPath", + "specVersion": "2.0.0", + "criticalService": "Copper cathode · Antofagasta → Shanghai", + "serviceOwner": "Copper Capital Syndicate", + "nodeRefs": [ + "urn:srcos:risk-node:escondida", + "urn:srcos:risk-node:antofagasta-port", + "urn:srcos:risk-node:route-anf-sha" + ], + "pathRisk": 0.48, + "rating": "High", + "impactTolerance": "< 2 weeks" +} diff --git a/schemas/RiskCluster.json b/schemas/RiskCluster.json new file mode 100644 index 0000000..58cfb78 --- /dev/null +++ b/schemas/RiskCluster.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/RiskCluster.json", + "title": "RiskCluster", + "description": "Common-mode / concentration cluster risk — correlated dependencies (a shared registry, CI provider, identity broker, model provider, or control plane). HHI measures concentration; blast radius, time-to-recover, and exit-difficulty size the systemic exposure.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "clusterName", + "hhi", + "residualCommonMode", + "rating" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^urn:srcos:risk-cluster:", + "description": "Stable URN. Pattern: urn:srcos:risk-cluster:" + }, + "type": { + "const": "RiskCluster", + "description": "Discriminator — always \"RiskCluster\"." + }, + "specVersion": { + "type": "string", + "description": "Spec version, e.g. \"2.0.0\"." + }, + "clusterName": { + "type": "string", + "minLength": 1 + }, + "clusterType": { + "type": [ + "string", + "null" + ] + }, + "providerShares": { + "type": "array", + "items": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "description": "Market/dependency share per provider (for HHI)." + }, + "hhi": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Herfindahl-Hirschman concentration index (normalized 0..1)." + }, + "blastRadius": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Fraction of the estate affected if the cluster fails." + }, + "timeToRecover": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Normalized recovery time (0=fast, 1=slow)." + }, + "exitDifficulty": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "How hard it is to switch away (lock-in)." + }, + "residualCommonMode": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Residual common-mode risk after resilience controls." + }, + "rating": { + "type": "string", + "enum": [ + "Low", + "Medium", + "High", + "Critical" + ] + } + } +} diff --git a/schemas/RiskIndicator.json b/schemas/RiskIndicator.json new file mode 100644 index 0000000..9e3ebdb --- /dev/null +++ b/schemas/RiskIndicator.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/RiskIndicator.json", + "title": "RiskIndicator", + "description": "A key risk indicator (KRI) or key control indicator (KCI) with green/amber/red thresholds, tracked against risk appetite.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "indicatorType", + "metric" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^urn:srcos:risk-indicator:", + "description": "Stable URN. Pattern: urn:srcos:risk-indicator:" + }, + "type": { + "const": "RiskIndicator", + "description": "Discriminator — always \"RiskIndicator\"." + }, + "specVersion": { + "type": "string", + "description": "Spec version, e.g. \"2.0.0\"." + }, + "indicatorType": { + "type": "string", + "enum": [ + "KRI", + "KCI" + ] + }, + "metric": { + "type": "string", + "minLength": 1 + }, + "definition": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "green": { + "type": [ + "string", + "null" + ] + }, + "amber": { + "type": [ + "string", + "null" + ] + }, + "red": { + "type": [ + "string", + "null" + ] + }, + "band": { + "type": [ + "string", + "null" + ], + "enum": [ + "green", + "amber", + "red", + null + ] + }, + "frequency": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "type": [ + "string", + "null" + ] + } + } +} diff --git a/schemas/RiskNode.json b/schemas/RiskNode.json new file mode 100644 index 0000000..f703fe1 --- /dev/null +++ b/schemas/RiskNode.json @@ -0,0 +1,176 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/RiskNode.json", + "title": "RiskNode", + "description": "Operational-risk assessment of a single supply-chain node. Inherent risk factors (criticality, privilege, execution, opacity, concentration, velocity) combine to an inherent score; control efficacy across six control families reduces it to a residual score + rating. Aligns to BIAN Operational Risk Models.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "nodeRef", + "inherent", + "inherentScore", + "residualScore", + "rating" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^urn:srcos:risk-node:", + "description": "Stable URN. Pattern: urn:srcos:risk-node:" + }, + "type": { + "const": "RiskNode", + "description": "Discriminator — always \"RiskNode\"." + }, + "specVersion": { + "type": "string", + "description": "Spec version, e.g. \"2.0.0\"." + }, + "nodeRef": { + "type": "string", + "description": "The supply-chain node/facility/route this assesses (e.g. an hg: ref)." + }, + "criticalService": { + "type": [ + "string", + "null" + ], + "description": "The critical service this node participates in." + }, + "inherent": { + "type": "object", + "additionalProperties": false, + "required": [ + "criticality", + "privilege", + "execution", + "opacity", + "concentration", + "velocity" + ], + "properties": { + "criticality": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "K — how critical the node is to a critical service." + }, + "privilege": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "P — authority/privilege the node holds." + }, + "execution": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "E — code/execution exposure." + }, + "opacity": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "O — how opaque/unverifiable the node is." + }, + "concentration": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "C — single-point / concentration." + }, + "velocity": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "V — change velocity / blast speed." + } + } + }, + "inherentScore": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Weighted inherent risk (0..1)." + }, + "controls": { + "type": "object", + "additionalProperties": false, + "required": [ + "governance", + "identityPublish", + "provenance", + "integrity", + "distribution", + "monitoring" + ], + "properties": { + "governance": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "governance control efficacy (0=none, 1=strong)." + }, + "identityPublish": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "identityPublish control efficacy (0=none, 1=strong)." + }, + "provenance": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "provenance control efficacy (0=none, 1=strong)." + }, + "integrity": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "integrity control efficacy (0=none, 1=strong)." + }, + "distribution": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "distribution control efficacy (0=none, 1=strong)." + }, + "monitoring": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "monitoring control efficacy (0=none, 1=strong)." + } + } + }, + "residualScore": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Residual risk after control efficacy (0..1)." + }, + "rating": { + "type": "string", + "enum": [ + "Low", + "Medium", + "High", + "Critical" + ] + }, + "controlGaps": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "description": "Control families below tier-0 minimum evidence." + }, + "note": { + "type": "string" + } + } +} diff --git a/schemas/RiskPath.json b/schemas/RiskPath.json new file mode 100644 index 0000000..1b9809f --- /dev/null +++ b/schemas/RiskPath.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/RiskPath.json", + "title": "RiskPath", + "description": "Critical-service path risk — accumulates node residual scores across a supply chain. A path is only as strong as its weakest admitted node; concentration along the path compounds risk.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "criticalService", + "nodeRefs", + "pathRisk", + "rating" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^urn:srcos:risk-path:", + "description": "Stable URN. Pattern: urn:srcos:risk-path:" + }, + "type": { + "const": "RiskPath", + "description": "Discriminator — always \"RiskPath\"." + }, + "specVersion": { + "type": "string", + "description": "Spec version, e.g. \"2.0.0\"." + }, + "criticalService": { + "type": "string", + "minLength": 1 + }, + "serviceOwner": { + "type": [ + "string", + "null" + ] + }, + "nodeRefs": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "pattern": "^urn:srcos:risk-node:" + }, + "description": "Ordered nodes on the critical service path." + }, + "pathRisk": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Accumulated path risk (0..1)." + }, + "rating": { + "type": "string", + "enum": [ + "Low", + "Medium", + "High", + "Critical" + ] + }, + "impactTolerance": { + "type": [ + "string", + "null" + ], + "description": "Tolerance for disruption (e.g. \"< 4h\")." + } + } +} diff --git a/tools/validate_supply_chain_risk_examples.py b/tools/validate_supply_chain_risk_examples.py new file mode 100644 index 0000000..d0ba98c --- /dev/null +++ b/tools/validate_supply_chain_risk_examples.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Validate the supply-chain operational-risk contract examples. + +A bank-style operational-risk framework applied to software/physical supply +chains (BIAN Operational Risk Models / FICO decisioning aligned): RiskNode +(inherent factors + control efficacy -> residual), RiskPath (accumulated node +residuals along a critical service), RiskCluster (common-mode / HHI concentration), +and RiskIndicator (KRI/KCI thresholds). +""" +from __future__ import annotations + +import json +from pathlib import Path + +import jsonschema + +ROOT = Path(__file__).resolve().parents[1] +PAIRS = [ + (ROOT / "schemas" / "RiskNode.json", ROOT / "examples" / "risk_node.json"), + (ROOT / "schemas" / "RiskPath.json", ROOT / "examples" / "risk_path.json"), + (ROOT / "schemas" / "RiskCluster.json", ROOT / "examples" / "risk_cluster.json"), + (ROOT / "schemas" / "RiskIndicator.json", ROOT / "examples" / "risk_indicator.json"), +] + + +def validate_pair(schema_path: Path, example_path: Path) -> None: + schema = json.loads(schema_path.read_text(encoding="utf-8")) + jsonschema.validators.validator_for(schema).check_schema(schema) + example = json.loads(example_path.read_text(encoding="utf-8")) + jsonschema.validate(example, schema) + + +def main() -> int: + checks: dict[str, bool] = {} + for schema_path, example_path in PAIRS: + validate_pair(schema_path, example_path) + checks[example_path.name] = True + print(json.dumps({"ok": all(checks.values()), "checks": checks}, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())