From a1bd9df8e4ca2988d37b5788d9fcefa96ecf9555 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 24 May 2026 03:00:29 +0200 Subject: [PATCH] feat: define accountability root manifest --- docs/accountability-root-manifest.md | 51 +++ docs/financial-fabric-operator-guide.md | 8 + .../accountability-root-manifest.yaml | 104 +++++ .../railiance-accountability-roots.yaml | 227 ++++++++++ .../accountability-root-manifest.schema.yaml | 402 ++++++++++++++++++ tests/test_accountability_roots.py | 64 +++ ...countability-root-discovery-update-loop.md | 22 +- 7 files changed, 875 insertions(+), 3 deletions(-) create mode 100644 docs/accountability-root-manifest.md create mode 100644 examples/discovery/accountability-root-manifest.yaml create mode 100644 fabric/discovery/railiance-accountability-roots.yaml create mode 100644 schemas/accountability-root-manifest.schema.yaml create mode 100644 tests/test_accountability_roots.py diff --git a/docs/accountability-root-manifest.md b/docs/accountability-root-manifest.md new file mode 100644 index 0000000..e426df4 --- /dev/null +++ b/docs/accountability-root-manifest.md @@ -0,0 +1,51 @@ +# Accountability Root Manifest + +The accountability root manifest is the handoff between the financial Fabric +model and the discovery/update loop. + +It answers where discovery starts. A manifest names the netkingdom, actors, +fabric boundaries, and durable roots that can prove repositories, deployment +realities, service configuration, endpoint contracts, backup/recovery evidence, +and secret-root metadata. It does not collect live telemetry and it does not +make State Hub the authoring surface for topology. + +Schema: + +```text +schemas/accountability-root-manifest.schema.yaml +``` + +Current Railiance manifest: + +```text +fabric/discovery/railiance-accountability-roots.yaml +``` + +Tenant/subfabric example: + +```text +examples/discovery/accountability-root-manifest.yaml +``` + +## Required Sections + +- `netkingdom`: root id, name, and king actor. +- `actors`: king, lord, tenant, operator, or steward actors. +- `fabrics`: fabric and subfabric boundaries. +- `discovery_roots`: durable roots such as State Hub repo inventory, Gitea + organizations, registry manifests, host paths, repo checkouts, deployment + automation, endpoint contracts, backup/recovery evidence, and secret-root + metadata. +- `refresh`: cadence and trigger hints for the future update loop. + +## Boundary Rules + +The current Railiance manifest has one active fabric: +`fabric.railiance.primary`. Future tenant subfabrics are added under that +fabric by adding a tenant actor, a `Subfabric`, and subfabric-scoped discovery +roots. This does not change the root fabric criterion: the fabric boundary +still rests on financial and operational accountability. + +Discovery roots should state `safe_discovery` explicitly. Secret and backup +roots should use `metadata_only` or `explicit_review`; adapters must never read +secret values or operational telemetry while building Fabric graph evidence. diff --git a/docs/financial-fabric-operator-guide.md b/docs/financial-fabric-operator-guide.md index 940b072..aad334b 100644 --- a/docs/financial-fabric-operator-guide.md +++ b/docs/financial-fabric-operator-guide.md @@ -42,6 +42,14 @@ Use the legacy JSON export for compatibility with existing `STATE-WP-0050` State Hub behavior. Use the financial export to verify the vNext contract and the ownership/fabric projection. +For accountability-root discovery, start from the current root manifest: + +```text +fabric/discovery/railiance-accountability-roots.yaml +``` + +The manifest schema is documented in `docs/accountability-root-manifest.md`. + The financial export must satisfy these invariants: - every accepted node has resolvable ownership; diff --git a/examples/discovery/accountability-root-manifest.yaml b/examples/discovery/accountability-root-manifest.yaml new file mode 100644 index 0000000..284b584 --- /dev/null +++ b/examples/discovery/accountability-root-manifest.yaml @@ -0,0 +1,104 @@ +apiVersion: railiance.fabric/v1alpha2 +kind: AccountabilityRootManifest +metadata: + id: example.accountability-roots + name: Example Accountability Roots + description: Minimal example showing how a tenant subfabric can be added without changing the parent fabric criterion. +netkingdom: + id: example.netkingdom + name: Example Netkingdom + king_actor_id: actor.example.king +actors: + - id: actor.example.king + role: king + name: Example King + authority: + recovery_authority: true + secrets_authority: true + backup_authority: true + termination_authority: true + - id: actor.example.lord + role: lord + name: Example Lord + - id: actor.example.tenant + role: tenant + name: Example Tenant +fabrics: + - id: fabric.example.primary + kind: Fabric + name: Example Primary Fabric + netkingdom_id: example.netkingdom + lord_actor_id: actor.example.lord + parent_fabric_id: null + status: active + boundary: + boundary_type: fabric + criterion: financial_and_operational_accountability + payment_responsibility: actor.example.lord + operational_responsibility: actor.example.king + recovery_responsibility: actor.example.king + - id: subfabric.example.tenant + kind: Subfabric + name: Example Tenant Subfabric + netkingdom_id: example.netkingdom + tenant_actor_id: actor.example.tenant + parent_fabric_id: fabric.example.primary + status: planned + boundary: + boundary_type: subfabric + criterion: restricted_paid_tenant_utility + payment_responsibility: actor.example.tenant + operational_responsibility: actor.example.lord + recovery_responsibility: actor.example.king +discovery_roots: + - id: root.example.state-hub + type: state_hub_repo_inventory + status: active + fabric_id: fabric.example.primary + owner_actor_id: actor.example.king + source: + base_url: http://127.0.0.1:8000 + api_paths: + - /managed-repos/ + safe_discovery: metadata_only + evidence_scope: + - repo_inventory + - repository_identity + refresh: + cadence: on_change + triggers: + - state_hub_repo_inventory_change + - operator_request + - id: root.example.tenant-api-contracts + type: endpoint_contract + status: planned + fabric_id: fabric.example.primary + subfabric_id: subfabric.example.tenant + owner_actor_id: actor.example.tenant + source: + repo_slug: example-tenant + path: contracts/openapi + safe_discovery: local_files + evidence_scope: + - endpoint_contract + - tenant_boundary + refresh: + cadence: on_change + triggers: + - endpoint_contract_change + - operator_request +refresh: + cadence: manual + triggers: + - operator_request + - state_hub_repo_inventory_change + - endpoint_contract_change + - lord_or_tenant_change +templates: + future_subfabric: + parent_fabric_id: fabric.example.primary + tenant_actor_role: tenant + required_updates: + - Add tenant actor. + - Add Subfabric with tenant_actor_id. + - Add subfabric-scoped discovery roots. diff --git a/fabric/discovery/railiance-accountability-roots.yaml b/fabric/discovery/railiance-accountability-roots.yaml new file mode 100644 index 0000000..8f00191 --- /dev/null +++ b/fabric/discovery/railiance-accountability-roots.yaml @@ -0,0 +1,227 @@ +apiVersion: railiance.fabric/v1alpha2 +kind: AccountabilityRootManifest +metadata: + id: railiance.accountability-roots + name: Railiance Accountability Roots + description: Current discovery roots for rebuilding the Railiance Fabric graph from durable accountability evidence. + source_links: + - label: Financial Fabric architecture + path: docs/FabricDiscoveryAndUpdate.md + - label: Financial baseline + path: fabric/financial/railiance-netkingdom.yaml +netkingdom: + id: railiance.netkingdom + name: Railiance Netkingdom + king_actor_id: actor.railiance.king + baseline_ref: + label: Railiance financial baseline + path: fabric/financial/railiance-netkingdom.yaml +actors: + - id: actor.railiance.king + role: king + name: Railiance King + description: Responsible for the Railiance netkingdom and recovery authority. + authority: + recovery_authority: true + secrets_authority: true + backup_authority: true + termination_authority: true + - id: actor.railiance.primary-lord + role: lord + name: Railiance Primary Lord + description: Pays for the current Railiance infrastructure boundary. +fabrics: + - id: fabric.railiance.primary + kind: Fabric + name: Railiance Primary Fabric + netkingdom_id: railiance.netkingdom + lord_actor_id: actor.railiance.primary-lord + parent_fabric_id: null + status: active + boundary: + boundary_type: fabric + criterion: financial_and_operational_accountability + payment_responsibility: actor.railiance.primary-lord + operational_responsibility: actor.railiance.king + recovery_responsibility: actor.railiance.king + evidence_refs: + - label: Railiance financial baseline + path: fabric/financial/railiance-netkingdom.yaml +discovery_roots: + - id: root.state-hub.attached-repos + type: state_hub_repo_inventory + status: active + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.king + source: + base_url: http://127.0.0.1:8000 + api_paths: + - /managed-repos/ + safe_discovery: metadata_only + evidence_scope: + - repo_inventory + - repository_identity + refresh: + cadence: on_change + triggers: + - state_hub_repo_inventory_change + - operator_request + notes: Read State Hub as repo inventory evidence only; State Hub does not author Fabric ownership or topology. + - id: root.gitea.coulomb + type: gitea_organization + status: active + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.king + source: + url: ssh://git@92.205.130.254:30022/coulomb + organization: coulomb + safe_discovery: metadata_only + evidence_scope: + - repo_inventory + - repository_identity + refresh: + cadence: on_change + triggers: + - git_commit + - operator_request + - id: root.registry.local-repos + type: registry_manifest + status: active + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.king + source: + manifest_path: registry/local-repos.yaml + safe_discovery: local_files + evidence_scope: + - repo_inventory + - repository_identity + - local_checkout + refresh: + cadence: on_change + triggers: + - state_hub_repo_inventory_change + - operator_request + - id: root.workspace.home-worsch + type: host_path + status: active + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.king + source: + path: /home/worsch + patterns: + - "*/.git" + - "*/fabric" + safe_discovery: local_files + evidence_scope: + - local_checkout + - repository_identity + refresh: + cadence: manual + triggers: + - operator_request + - id: root.railiance-fabric.checkout + type: repository_checkout + status: active + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.primary-lord + source: + repo_slug: railiance-fabric + path: /home/worsch/railiance-fabric + remote_url: gitea-remote:coulomb/railiance-fabric.git + safe_discovery: local_files + evidence_scope: + - repository_identity + - local_checkout + - service_configuration + - endpoint_contract + - deployment_topology + refresh: + cadence: on_change + triggers: + - git_commit + - deployment_manifest_change + - endpoint_contract_change + - operator_request + - id: root.deployment.local-manifests + type: deployment_automation + status: active + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.king + source: + path: /home/worsch + patterns: + - "**/compose.yaml" + - "**/compose.yml" + - "**/docker-compose.yaml" + - "**/Dockerfile" + - "**/*.service" + - "**/k8s/*.yaml" + - "**/deploy*.sh" + safe_discovery: local_files + evidence_scope: + - deployment_topology + - infrastructure + - service_configuration + refresh: + cadence: on_change + triggers: + - deployment_manifest_change + - infrastructure_manifest_change + - operator_request + - id: root.openbao.secret-metadata + type: secret_root + status: planned + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.king + source: + repo_slug: railiance-fabric + path: fabric/services/railiance-platform-openbao.yaml + safe_discovery: metadata_only + evidence_scope: + - secret_metadata + - infrastructure + refresh: + cadence: manual + triggers: + - secret_root_change + - operator_request + notes: Discover only existence and metadata for secret roots; never extract secret values. + - id: root.backup-recovery.metadata + type: backup_recovery + status: planned + fabric_id: fabric.railiance.primary + owner_actor_id: actor.railiance.king + source: + path: docs/financial-fabric-operator-guide.md + safe_discovery: explicit_review + evidence_scope: + - backup_recovery + - manual_review + refresh: + cadence: manual + triggers: + - backup_recovery_change + - operator_request +refresh: + cadence: manual + triggers: + - operator_request + - state_hub_repo_inventory_change + - git_commit + - deployment_manifest_change + - infrastructure_manifest_change + - endpoint_contract_change + - secret_root_change + - backup_recovery_change + - lord_or_tenant_change + notes: Manual rebuild is the default until snapshot deltas and freshness triggers are implemented. +templates: + future_subfabric: + parent_fabric_id: fabric.railiance.primary + tenant_actor_role: tenant + required_updates: + - Add tenant actor with role tenant. + - Add Subfabric under fabric.railiance.primary with tenant_actor_id. + - Add tenant-specific discovery roots with subfabric_id. + - Add cross-boundary utility edges with provider and consumer owner context. + note: Tenant subfabrics do not change the current root fabric criterion. diff --git a/schemas/accountability-root-manifest.schema.yaml b/schemas/accountability-root-manifest.schema.yaml new file mode 100644 index 0000000..7b4fbfc --- /dev/null +++ b/schemas/accountability-root-manifest.schema.yaml @@ -0,0 +1,402 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://railiance.local/fabric/schemas/accountability-root-manifest.schema.yaml" +title: "AccountabilityRootManifest" +type: object +additionalProperties: false +required: + - apiVersion + - kind + - metadata + - netkingdom + - actors + - fabrics + - discovery_roots + - refresh +properties: + apiVersion: + type: string + const: "railiance.fabric/v1alpha2" + kind: + type: string + const: AccountabilityRootManifest + metadata: + type: object + additionalProperties: false + required: + - id + - name + properties: + id: + $ref: "#/$defs/stableId" + name: + type: string + minLength: 1 + description: + type: string + source_links: + type: array + items: + $ref: "#/$defs/sourceLink" + netkingdom: + $ref: "#/$defs/netkingdom" + actors: + type: array + minItems: 1 + items: + $ref: "#/$defs/actor" + fabrics: + type: array + minItems: 1 + items: + $ref: "#/$defs/fabric" + discovery_roots: + type: array + minItems: 1 + items: + $ref: "#/$defs/discoveryRoot" + refresh: + $ref: "#/$defs/refreshPolicy" + templates: + type: object + additionalProperties: false + properties: + future_subfabric: + $ref: "#/$defs/futureSubfabricTemplate" + +$defs: + stableId: + type: string + minLength: 3 + maxLength: 180 + pattern: "^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$" + + pathString: + type: string + minLength: 1 + + sourceLink: + type: object + additionalProperties: false + required: + - label + properties: + label: + type: string + minLength: 1 + path: + type: string + minLength: 1 + url: + type: string + minLength: 1 + ref: + type: string + minLength: 1 + anyOf: + - required: [path] + - required: [url] + - required: [ref] + + netkingdom: + type: object + additionalProperties: false + required: + - id + - name + - king_actor_id + properties: + id: + $ref: "#/$defs/stableId" + name: + type: string + minLength: 1 + king_actor_id: + $ref: "#/$defs/stableId" + baseline_ref: + $ref: "#/$defs/sourceLink" + + actor: + type: object + additionalProperties: false + required: + - id + - role + - name + properties: + id: + $ref: "#/$defs/stableId" + role: + type: string + enum: + - king + - lord + - tenant + - operator + - steward + name: + type: string + minLength: 1 + description: + type: string + authority: + type: object + additionalProperties: false + properties: + recovery_authority: + type: boolean + secrets_authority: + type: boolean + backup_authority: + type: boolean + termination_authority: + type: boolean + evidence_refs: + type: array + items: + $ref: "#/$defs/sourceLink" + + fabric: + type: object + additionalProperties: false + required: + - id + - kind + - name + - netkingdom_id + - status + - boundary + properties: + id: + $ref: "#/$defs/stableId" + kind: + type: string + enum: + - Fabric + - Subfabric + name: + type: string + minLength: 1 + netkingdom_id: + $ref: "#/$defs/stableId" + lord_actor_id: + $ref: "#/$defs/stableId" + tenant_actor_id: + $ref: "#/$defs/stableId" + parent_fabric_id: + oneOf: + - $ref: "#/$defs/stableId" + - type: "null" + status: + type: string + enum: + - active + - planned + - retired + boundary: + type: object + additionalProperties: false + required: + - boundary_type + - criterion + properties: + boundary_type: + type: string + enum: + - fabric + - subfabric + criterion: + type: string + minLength: 1 + payment_responsibility: + $ref: "#/$defs/stableId" + operational_responsibility: + $ref: "#/$defs/stableId" + recovery_responsibility: + $ref: "#/$defs/stableId" + evidence_refs: + type: array + items: + $ref: "#/$defs/sourceLink" + allOf: + - if: + properties: + kind: + const: Fabric + then: + required: + - lord_actor_id + - if: + properties: + kind: + const: Subfabric + then: + required: + - parent_fabric_id + - tenant_actor_id + + discoveryRoot: + type: object + additionalProperties: false + required: + - id + - type + - status + - fabric_id + - owner_actor_id + - source + - evidence_scope + properties: + id: + $ref: "#/$defs/stableId" + type: + type: string + enum: + - state_hub_repo_inventory + - gitea_organization + - gitea_repository + - registry_manifest + - repository_checkout + - host_path + - deployment_automation + - infrastructure_manifest + - service_config + - endpoint_contract + - backup_recovery + - secret_root + - manual_review_queue + status: + type: string + enum: + - active + - planned + - disabled + fabric_id: + $ref: "#/$defs/stableId" + subfabric_id: + $ref: "#/$defs/stableId" + owner_actor_id: + $ref: "#/$defs/stableId" + source: + type: object + additionalProperties: false + properties: + base_url: + type: string + minLength: 1 + url: + type: string + minLength: 1 + organization: + type: string + minLength: 1 + repo_slug: + type: string + minLength: 1 + path: + $ref: "#/$defs/pathString" + remote_url: + type: string + minLength: 1 + manifest_path: + $ref: "#/$defs/pathString" + api_paths: + type: array + items: + type: string + minLength: 1 + patterns: + type: array + items: + type: string + minLength: 1 + safe_discovery: + type: string + enum: + - metadata_only + - local_files + - content_hash + - explicit_review + evidence_scope: + type: array + minItems: 1 + uniqueItems: true + items: + type: string + enum: + - repo_inventory + - repository_identity + - local_checkout + - deployment_topology + - service_configuration + - infrastructure + - endpoint_contract + - backup_recovery + - secret_metadata + - fabric_boundary + - tenant_boundary + - accounting_boundary + - manual_review + refresh: + $ref: "#/$defs/refreshPolicy" + evidence_refs: + type: array + items: + $ref: "#/$defs/sourceLink" + notes: + type: string + + refreshPolicy: + type: object + additionalProperties: false + required: + - cadence + - triggers + properties: + cadence: + type: string + enum: + - manual + - hourly + - daily + - weekly + - on_change + triggers: + type: array + uniqueItems: true + items: + type: string + enum: + - operator_request + - state_hub_repo_inventory_change + - git_commit + - deployment_manifest_change + - infrastructure_manifest_change + - endpoint_contract_change + - secret_root_change + - backup_recovery_change + - lord_or_tenant_change + - scheduled_rebuild + notes: + type: string + + futureSubfabricTemplate: + type: object + additionalProperties: false + required: + - parent_fabric_id + - tenant_actor_role + - required_updates + properties: + parent_fabric_id: + $ref: "#/$defs/stableId" + tenant_actor_role: + type: string + const: tenant + required_updates: + type: array + minItems: 1 + items: + type: string + minLength: 1 + note: + type: string diff --git a/tests/test_accountability_roots.py b/tests/test_accountability_roots.py new file mode 100644 index 0000000..6d158a9 --- /dev/null +++ b/tests/test_accountability_roots.py @@ -0,0 +1,64 @@ +from pathlib import Path + +from railiance_fabric.financial_baseline import load_financial_baseline +from railiance_fabric.loader import load_yaml +from railiance_fabric.schema_validation import draft202012_validator + + +MANIFEST_PATH = Path("fabric/discovery/railiance-accountability-roots.yaml") +EXAMPLE_PATH = Path("examples/discovery/accountability-root-manifest.yaml") +SCHEMA_PATH = Path("schemas/accountability-root-manifest.schema.yaml") + + +def test_accountability_root_manifests_match_schema() -> None: + validator = draft202012_validator(SCHEMA_PATH) + + for path in (MANIFEST_PATH, EXAMPLE_PATH): + manifest = load_yaml(path) + assert list(validator.iter_errors(manifest)) == [] + + +def test_railiance_manifest_matches_financial_baseline() -> None: + manifest = load_yaml(MANIFEST_PATH) + baseline = load_financial_baseline() + + assert manifest["netkingdom"]["id"] == baseline["netkingdom"]["id"] + assert manifest["netkingdom"]["king_actor_id"] == baseline["netkingdom"]["king_actor_id"] + + baseline_actor_ids = {actor["id"] for actor in baseline["actors"]} + manifest_actor_ids = {actor["id"] for actor in manifest["actors"]} + assert baseline_actor_ids <= manifest_actor_ids + + primary_fabric = next(fabric for fabric in manifest["fabrics"] if fabric["id"] == "fabric.railiance.primary") + assert primary_fabric["kind"] == "Fabric" + assert primary_fabric["status"] == "active" + assert primary_fabric["boundary"]["criterion"] == "financial_and_operational_accountability" + + +def test_railiance_manifest_covers_required_root_kinds() -> None: + manifest = load_yaml(MANIFEST_PATH) + root_types = {root["type"] for root in manifest["discovery_roots"]} + + assert { + "state_hub_repo_inventory", + "gitea_organization", + "registry_manifest", + "host_path", + "repository_checkout", + "deployment_automation", + "backup_recovery", + "secret_root", + } <= root_types + + assert all(root["fabric_id"] == "fabric.railiance.primary" for root in manifest["discovery_roots"]) + assert all(root["source"]["safe_discovery"] for root in manifest["discovery_roots"]) + + +def test_example_manifest_can_add_tenant_subfabric() -> None: + manifest = load_yaml(EXAMPLE_PATH) + subfabrics = [fabric for fabric in manifest["fabrics"] if fabric["kind"] == "Subfabric"] + + assert len(subfabrics) == 1 + assert subfabrics[0]["parent_fabric_id"] == "fabric.example.primary" + assert subfabrics[0]["tenant_actor_id"] == "actor.example.tenant" + assert any(root.get("subfabric_id") == "subfabric.example.tenant" for root in manifest["discovery_roots"]) diff --git a/workplans/RAIL-FAB-WP-0018-accountability-root-discovery-update-loop.md b/workplans/RAIL-FAB-WP-0018-accountability-root-discovery-update-loop.md index 774d00f..f7e68eb 100644 --- a/workplans/RAIL-FAB-WP-0018-accountability-root-discovery-update-loop.md +++ b/workplans/RAIL-FAB-WP-0018-accountability-root-discovery-update-loop.md @@ -4,7 +4,7 @@ type: workplan title: "Accountability Root Discovery And Update Loop" domain: railiance repo: railiance-fabric -status: ready +status: active owner: codex topic_slug: railiance created: "2026-05-23" @@ -39,7 +39,7 @@ interfaces, or cross-boundary utility. ```task id: RAIL-FAB-WP-0018-T01 -status: todo +status: done priority: high state_hub_task_id: "38ae49fb-ce21-489c-ba67-7f76ab4febc9" ``` @@ -65,6 +65,23 @@ Done when: - the format can add future tenant subfabrics without changing the top-level fabric criterion. +Result: + +- Added `schemas/accountability-root-manifest.schema.yaml` for + `railiance.fabric/v1alpha2` accountability-root manifests. +- Added `fabric/discovery/railiance-accountability-roots.yaml` to represent + the current Railiance one-fabric baseline, State Hub/Gitea/repo/host roots, + deployment automation roots, and safe metadata-only secret/backup roots. +- Added `examples/discovery/accountability-root-manifest.yaml` to show how a + tenant subfabric can be added without changing the parent fabric criterion. +- Added `docs/accountability-root-manifest.md` and linked the manifest from the + financial Fabric operator guide. +- Added schema/baseline coverage in `tests/test_accountability_roots.py`. +- Verified with `python3 -m pytest tests/test_accountability_roots.py -q`, + `python3 -m railiance_fabric.cli validate .`, + `python3 -m railiance_fabric.cli export --format financial .`, and full + `python3 -m pytest`. + ## T02 - Implement Durable Evidence Discovery Adapters ```task @@ -210,4 +227,3 @@ Done when: and cross-boundary utility changes. - Live telemetry remains out of scope. - The current Railiance baseline can be rebuilt from scratch and exported. -