Establish Railiance Fabric graph model

This commit is contained in:
2026-05-17 19:47:37 +02:00
parent 9c1f4d1381
commit 19f9fddc35
89 changed files with 5007 additions and 2 deletions

207
docs/adoption-guide.md Normal file
View File

@@ -0,0 +1,207 @@
# Adoption Guide
This guide shows another repo how to adopt Railiance Fabric declarations without
reading Railiance Fabric source code.
## 1. Add The Directory Layout
Create the declaration directories in your repo:
```text
fabric/
services/
capabilities/
interfaces/
dependencies/
bindings/
```
Start with only the files you need. A repo can adopt Fabric with one service
and one capability, or with one dependency on a capability provided elsewhere.
## 2. Declare A Service
Create `fabric/services/<repo>-<service>.yaml`:
```yaml
apiVersion: railiance.fabric/v1alpha1
kind: ServiceDeclaration
metadata:
id: your-repo.your-service
name: Your Service
owner: your-repo
repo: your-repo
domain: railiance
source_links:
- label: Service README
path: README.md
spec:
lifecycle: active
environments: [dev, staging, prod]
description: What this service does.
service_type: app-service
provides_capabilities: []
exposes_interfaces: []
```
Use lower-case dotted IDs. Prefer IDs that begin with the owning repo slug.
## 3. Declare A Provided Capability
Create `fabric/capabilities/<repo>-<capability>.yaml`:
```yaml
apiVersion: railiance.fabric/v1alpha1
kind: CapabilityDeclaration
metadata:
id: your-repo.your-service.runtime-secrets
name: Runtime secrets
owner: your-repo
repo: your-repo
domain: railiance
source_links:
- label: Capability docs
path: docs/runtime-secrets.md
spec:
lifecycle: active
environments: [dev, staging, prod]
description: What this capability provides.
capability_type: runtime-secrets
service_id: your-repo.your-service
interface_ids:
- your-repo.your-service.kv-v2
criticality: high
data_classification: secret
```
Pick `capability_type` from `catalog/capability-types.yaml`.
## 4. Declare An Interface
Create `fabric/interfaces/<repo>-<interface>.yaml`:
```yaml
apiVersion: railiance.fabric/v1alpha1
kind: InterfaceDeclaration
metadata:
id: your-repo.your-service.http-api
name: Your Service HTTP API
owner: your-repo
repo: your-repo
domain: railiance
source_links:
- label: API docs
path: docs/api.md
spec:
lifecycle: active
environments: [dev, staging, prod]
description: How consumers call this interface.
interface_type: http-api
version: v1
service_id: your-repo.your-service
capability_ids:
- your-repo.your-service.some-capability
auth:
method: oidc
data_classification: internal
```
Pick `interface_type` from `catalog/interface-types.yaml`.
## 5. Declare A Dependency
Create `fabric/dependencies/<repo>-<dependency>.yaml`:
```yaml
apiVersion: railiance.fabric/v1alpha1
kind: DependencyDeclaration
metadata:
id: your-repo.your-service.needs-runtime-secrets
name: Runtime secrets dependency
owner: your-repo
repo: your-repo
domain: railiance
source_links:
- label: Deployment values
path: deploy/values.yaml
spec:
lifecycle: active
environments: [dev, staging, prod]
consumer_service_id: your-repo.your-service
requires:
capability_type: runtime-secrets
interface:
type: openbao-kv-v2-mount
version_constraint: ">=v1 <v2"
auth:
method: kubernetes_service_account
criticality: high
data_classification: secret
fallback:
mode: none
description: Service cannot start without runtime secrets.
```
Active production dependencies should include at least one source link.
## 6. Add A Binding Only When Needed
Most bindings can be computed by the graph loader from dependency requirements
and provider capabilities. Add a `BindingAssertion` when you need to pin,
override, dispute, or document a planned provider:
```yaml
apiVersion: railiance.fabric/v1alpha1
kind: BindingAssertion
metadata:
id: your-repo.your-service.runtime-secrets-to-openbao
name: Runtime secrets binding
owner: your-repo
repo: your-repo
domain: railiance
spec:
lifecycle: active
environments: [dev, staging, prod]
dependency_id: your-repo.your-service.needs-runtime-secrets
provider_capability_id: railiance-platform.openbao.runtime-secrets
provider_interface_id: railiance-platform.openbao.kv-v2
status: compatible
rationale: This service uses OpenBao KV v2 for runtime secrets.
```
## 7. Validate Locally
From the Railiance Fabric repo:
```bash
railiance-fabric validate /path/to/your-repo
```
During early bootstrapping:
```bash
PYTHONPATH=. python -m railiance_fabric.cli validate /path/to/your-repo
```
Useful discovery checks:
```bash
railiance-fabric providers runtime-secrets /path/to/your-repo
railiance-fabric consumers runtime-secrets /path/to/your-repo
railiance-fabric unresolved /path/to/your-repo
```
For multi-repo validation, pass multiple roots:
```bash
railiance-fabric validate /path/to/repo-a /path/to/repo-b
```
## 8. Export For State Hub
```bash
railiance-fabric export --format json /path/to/your-repo
```
State Hub should ingest that export as a read model. Do not edit declarations in
State Hub; change them in the owning repo and re-export.

252
docs/declaration-schema.md Normal file
View File

@@ -0,0 +1,252 @@
# Declaration Schema
Railiance Fabric declarations are small YAML documents owned by the repository
that provides or consumes the declared thing. The first schema version is
`railiance.fabric/v1alpha1`.
## File Layout
Participating repositories should use this layout:
```text
fabric/
services/*.yaml
capabilities/*.yaml
interfaces/*.yaml
dependencies/*.yaml
bindings/*.yaml
```
Railiance Fabric itself keeps reusable schemas in `schemas/` and examples in
`examples/declarations/`.
## Shared Shape
Every declaration has:
```yaml
apiVersion: railiance.fabric/v1alpha1
kind: ServiceDeclaration
metadata:
id: railiance-platform.openbao
name: OpenBao
owner: railiance-platform
repo: railiance-platform
domain: railiance
spec:
lifecycle: active
environments: [dev, staging, prod]
```
`metadata.id` is the stable graph identifier. Use lower-case dotted IDs that
begin with the owning repo slug when possible:
```text
<repo>.<service>
<repo>.<service>.<capability>
<repo>.<service>.<interface>
<repo>.<consumer>.<dependency>
```
## Shared Fields
| Field | Meaning |
|-------|---------|
| `apiVersion` | Schema API version. Currently `railiance.fabric/v1alpha1`. |
| `kind` | Declaration kind: service, capability, interface, dependency, or binding assertion. |
| `metadata.id` | Stable graph identifier used for references and bindings. |
| `metadata.name` | Human-readable display name. |
| `metadata.owner` | Owning team, repo, or domain owner. |
| `metadata.repo` | Repo slug that owns the declaration. |
| `metadata.domain` | Domain slug, such as `railiance` or `custodian`. |
| `metadata.source_links` | Optional source pointers to docs, code, manifests, ADRs, or workplans. |
| `spec.lifecycle` | `planned`, `active`, `deprecated`, or `retired`. |
| `spec.environments` | One or more of `dev`, `staging`, `prod`, or `all`. |
## Declaration Kinds
### ServiceDeclaration
A deployable or callable unit produced by a repo.
Required type-specific fields:
- `spec.description`
Optional relationship fields:
- `spec.service_type`
- `spec.provides_capabilities`
- `spec.exposes_interfaces`
Schema: `schemas/service.schema.yaml`
### CapabilityDeclaration
A stable semantic ability that consumers depend on.
Required type-specific fields:
- `spec.description`
- `spec.capability_type`
- `spec.service_id`
- `spec.criticality`
- `spec.data_classification`
Optional relationship fields:
- `spec.interface_ids`
- `spec.compatibility`
Schema: `schemas/capability.schema.yaml`
`spec.capability_type` should match a type in
`catalog/capability-types.yaml`. Unknown types are allowed by the document
schema but should fail graph validation.
### InterfaceDeclaration
A concrete integration surface through which a capability is consumed.
Required type-specific fields:
- `spec.description`
- `spec.interface_type`
- `spec.version`
- `spec.service_id`
- `spec.auth.method`
- `spec.data_classification`
Optional relationship fields:
- `spec.capability_ids`
- `spec.endpoint`
- `spec.auth.audience`
- `spec.auth.scopes`
- `spec.compatibility`
Schema: `schemas/interface.schema.yaml`
`spec.interface_type` should match a type in `catalog/interface-types.yaml`.
Unknown types are allowed by the document schema but should fail graph
validation.
### DependencyDeclaration
A consumer's declared requirement for a capability or interface.
Required type-specific fields:
- `spec.consumer_service_id`
- `spec.requires.capability_type`
- `spec.criticality`
- `spec.data_classification`
Optional constraint fields:
- `spec.requires.capability_id`
- `spec.interface.type`
- `spec.interface.version_constraint`
- `spec.auth.method`
- `spec.fallback`
- `spec.compatibility`
Schema: `schemas/dependency.schema.yaml`
`spec.requires.capability_type` and `spec.interface.type` should match the
type catalogs. Unknown types are allowed by the document schema but should fail
graph validation.
### BindingAssertion
A source-controlled assertion that resolves a dependency to a provider
capability and, optionally, a provider interface. Most bindings should be
computed by the graph loader; binding assertions are for overrides, disputes,
or planned relationships that need an explicit record.
Required type-specific fields:
- `spec.dependency_id`
- `spec.provider_capability_id`
- `spec.status`
- `spec.rationale`
Optional relationship fields:
- `spec.provider_interface_id`
- `spec.compatibility`
Schema: `schemas/binding.schema.yaml`
## Shared Value Sets
### Lifecycle
```text
planned, active, deprecated, retired
```
### Environment
```text
dev, staging, prod, all
```
Use `all` only when the declaration truly applies across every environment.
### Data Classification
```text
public, internal, confidential, restricted, secret
```
### Criticality
```text
low, medium, high, critical
```
### Auth Method
```text
none, oidc, jwt, mtls, kubernetes_service_account, openbao_token,
static_secret, database_role, sts_token, api_key, unknown
```
`unknown` is allowed for discovery-stage declarations but should not remain on
active production dependencies.
## Compatibility
The optional `compatibility` object records machine-checkable or human-reviewed
constraints:
```yaml
compatibility:
version: "v1"
requires:
- "decision-envelope >=1.0 <2.0"
compatible_with:
- "flex-auth.decision-api.v1"
breaks:
- "decision-envelope v0"
notes: "Envelope v1 is required for tenant-scoped decisions."
```
T05 will decide which compatibility fields are advisory and which should fail
validation.
## Source Links
Use `metadata.source_links` when a declaration is based on a concrete source:
```yaml
source_links:
- label: OpenBao Helm values
path: charts/openbao/values.yaml
- label: Runtime secrets workplan
url: https://example.invalid/workplans/openbao-runtime-secrets
```
At least one source link is recommended for `active` declarations. T05 will
make source-link requirements stricter for active production dependencies.

85
docs/discovery-queries.md Normal file
View File

@@ -0,0 +1,85 @@
# Discovery Queries And Exports
Railiance Fabric includes a first CLI surface for inspecting local declaration
graphs.
All commands accept a repo root, `fabric/` directory, or declaration files. When
paths are omitted, commands read `./fabric`.
## Providers
List providers for a capability type or capability id:
```bash
railiance-fabric providers runtime-secrets
railiance-fabric providers railiance-platform.openbao.runtime-secrets
```
Output columns:
```text
provider_id service_id lifecycle environments interfaces
```
## Consumers
List consumers of a capability type/id or interface type/id:
```bash
railiance-fabric consumers runtime-secrets
railiance-fabric consumers railiance-platform.openbao.kv-v2
```
Output columns:
```text
consumer_service_id dependency_id requires provider_capability_id provider_interface_id status
```
## Dependency Path
Show dependency paths for a service:
```bash
railiance-fabric dependency-path flex-auth.api
```
This walks declared dependencies and binding assertions recursively through
provider services.
## Unresolved Dependencies
Show dependencies with no matching provider or a `missing`/`disputed` binding:
```bash
railiance-fabric unresolved
```
## Blast Radius
Show consumers affected by an interface type or interface id:
```bash
railiance-fabric blast-radius openbao-kv-v2-mount
railiance-fabric blast-radius railiance-platform.openbao.kv-v2
```
## Exports
Export the graph as JSON:
```bash
railiance-fabric export --format json
```
Export the graph as Mermaid:
```bash
railiance-fabric export --format mermaid
```
The JSON export has two top-level arrays:
- `nodes`: service, capability, interface, dependency, and binding nodes
- `edges`: graph relationships such as `provides`, `exposes`,
`available_via`, `consumes`, `binds:<status>`, and `uses_interface`

View File

@@ -0,0 +1,200 @@
# Ecosystem Registry Service Direction
This note compares Railiance Fabric with adjacent projects and standards before
starting a service implementation for registering repositories, libraries,
services, capabilities, interfaces, and dependencies.
## Recommendation
Build a small Railiance Ecosystem Registry service as the API and indexed read
model over repo-owned Fabric declarations.
The registry should not replace the `fabric/` files in each repo. Repositories
remain the source of truth. The service validates, snapshots, queries, and
projects that model so agents, humans, and State Hub can interact with the
ecosystem graph without cloning every repo or rerunning the local CLI.
The closest external model to compare against is CNCF xRegistry. xRegistry is
specifically about metadata registries, with both file/document and API views.
Railiance should borrow that shape where useful, especially for versioned
resources, import/export, filtering, and contract registries. Railiance should
not begin by claiming xRegistry compliance; it should keep a compatible path.
## Standards And Projects To Compare
| Project or standard | What it is good at | What Railiance should borrow | What not to copy as the core |
|---------------------|--------------------|-------------------------------|------------------------------|
| [CNCF xRegistry](https://xregistry.io/) | Vendor-neutral metadata registries with REST APIs, document views, versioned resources, endpoint/schema/message extensions. | Use as the primary comparison for registry API shape, versioned metadata, import/export, filtering, document/API symmetry, and future endpoint/message/schema projections. | Do not make every Fabric concept an xRegistry resource on day one; keep the Railiance graph model readable and repo-native first. |
| [Backstage Software Catalog](https://github.com/backstage/backstage/blob/master/docs/features/software-catalog/descriptor-format.md) | Developer portal catalog entities such as Component, API, Resource, System, Domain, ownership, relations, and `catalog-info.yaml`. | Support Backstage export/import projections for teams that want a portal later. Borrow the ownership and domain/system vocabulary where it aligns. | Do not make Backstage the authoritative store or require its plugin/runtime model before Railiance needs a portal. |
| [CycloneDX](https://cyclonedx.org/specification/overview/) | Supply-chain inventory for components, services, dependencies, relationships, and vulnerability/security context. | Use CycloneDX SBOM/SaaSBOM imports for libraries, packages, third-party services, component dependency graphs, and provenance facets. | Do not stretch CycloneDX into the whole ecosystem model; it is strongest for bill-of-materials and supply-chain evidence. |
| [OpenAPI](https://spec.openapis.org/oas/latest) | Machine-readable HTTP API contracts. | Attach OpenAPI documents to Fabric `InterfaceDeclaration` records for HTTP APIs and expose them through the registry. | Do not use OpenAPI to describe non-HTTP dependencies or ownership relationships. |
| [AsyncAPI](https://www.asyncapi.com/docs/reference/specification/v3.0.0) | Machine-readable event/message-driven API contracts with channels, messages, operations, and protocol bindings. | Attach AsyncAPI documents to event-stream interfaces and use its vocabulary for channel/message contracts. | Do not use AsyncAPI for general service inventory. |
| [CloudEvents](https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md) | Common event envelope metadata across services, platforms, and systems. | Use CloudEvents as the preferred event envelope for registry events and for Fabric event interfaces when the ecosystem needs portable event metadata. | Do not use CloudEvents as a catalog model; it is an event envelope. |
| [Open Service Broker API](https://www.openservicebrokerapi.org/) | Lifecycle commands for service catalogs, provisioning, binding, unbinding, and deprovisioning. | Borrow the clear distinction between service catalog, service instance, and binding if Railiance later adds self-service provisioning. | Do not implement provisioning as part of the initial registry; registration and discovery come first. |
| [Score](https://developer.humanitec.com/app-humanitec-io/docs/score/overview/) | Platform-agnostic workload intent for container workloads, with runtime requirements resolved by a platform. | Optionally import workload requirements into Fabric dependencies when repos already use Score. | Do not make Score mandatory; it is workload runtime intent, not an ecosystem graph. |
| [OpenLineage](https://openlineage.io/docs/spec/object-model/) | Job, run, dataset, and facet model for observing data movement and transformation. | Use its facet idea for extensible metadata and consider data-lineage projections later. | Do not use it as the general service/capability/dependency model. |
## Service Boundary
The registry service should own:
- repository registration and repository metadata snapshots
- ingestion of validated Fabric graph exports
- validation results per repo and commit
- indexed graph queries across all registered repos
- version history and drift comparisons between snapshots
- optional ingestion of supporting artifacts such as CycloneDX SBOMs,
OpenAPI documents, AsyncAPI documents, and Score workload files
- State Hub export or event emission for coordination views
The registry service should not own:
- hand-editing repo declarations through a central UI
- deployment orchestration
- service provisioning
- policy enforcement gates before the model has adoption
- replacing State Hub workstreams, tasks, progress, or planning state
- replacing a developer portal
## Initial Data Model
Railiance Fabric already has first-class declarations for:
- `ServiceDeclaration`
- `CapabilityDeclaration`
- `InterfaceDeclaration`
- `DependencyDeclaration`
- `BindingAssertion`
The registry service should add service-level records around those declarations:
| Entity | Purpose |
|--------|---------|
| Repository | Registered source repo, URL, default branch, State Hub repo id, scan config, last accepted snapshot. |
| Snapshot | Immutable ingest result for a repo at a commit. |
| Graph Node | Indexed projection of a service, capability, interface, dependency, or binding. |
| Graph Edge | Indexed relationship such as provides, exposes, consumes, binds, or uses interface. |
| Artifact | Supporting document such as CycloneDX SBOM, OpenAPI, AsyncAPI, Score, README, or source link. |
| Validation Result | Errors, warnings, schema versions, catalog versions, and unresolved references. |
| Registry Event | Change event emitted when a repo, snapshot, node, edge, or validation result changes. |
## API Shape
Start with a small HTTP API that mirrors the local CLI answers:
```text
POST /repositories
GET /repositories
GET /repositories/{repo_slug}
POST /repositories/{repo_slug}/snapshots
GET /repositories/{repo_slug}/snapshots
GET /repositories/{repo_slug}/snapshots/latest
GET /graph/nodes
GET /graph/nodes/{graph_id}
GET /graph/providers?capability_type=runtime-secrets
GET /graph/consumers?target=railiance-platform.openbao.kv-v2
GET /graph/unresolved
GET /graph/blast-radius?interface_id=openbao-kv-v2-mount
POST /artifacts
GET /artifacts/{artifact_id}
GET /exports/state-hub
GET /exports/backstage
GET /exports/xregistry
```
`POST /repositories/{repo_slug}/snapshots` should accept the current
`FabricGraphExport` plus source metadata:
```json
{
"repo_slug": "railiance-fabric",
"commit": "git-sha",
"generated_at": "2026-05-17T00:00:00Z",
"graph": {
"apiVersion": "railiance.fabric/v1alpha1",
"kind": "FabricGraphExport",
"nodes": [],
"edges": []
}
}
```
## Interoperability Direction
The first implementation should be Railiance-native but deliberately
projection-friendly:
- Backstage projection: export `Component`, `API`, `Resource`, `System`, and
`Domain` entities from Fabric nodes where possible.
- xRegistry projection: expose schemas, messages, endpoints, and possibly
Fabric-specific registry groups once the internal model settles.
- CycloneDX import: attach SBOM components, services, and dependencies to repo
and service nodes.
- OpenAPI/AsyncAPI attachment: connect contract documents to interface nodes
and validate that declared interface type/version metadata is consistent.
- CloudEvents events: emit registry changes such as
`railiance.fabric.repository.registered`,
`railiance.fabric.snapshot.accepted`, and
`railiance.fabric.validation.failed`.
- Score import: map workload resources and dependencies into draft Fabric
dependency declarations only when a repo opts in.
## Suggested Architecture
```text
repo-local fabric/*.yaml
|
v
railiance-fabric validate/export
|
v
Ecosystem Registry ingest API
|
+--> snapshot store
+--> graph index
+--> artifact index
+--> validation result store
|
+--> State Hub export/events
+--> Backstage/xRegistry projections
+--> query API for agents and humans
```
Keep the first service boring: the existing Python loader and validator should
be reused. A lightweight Python HTTP service with a local relational store is
enough for the first useful version. Once ingestion and query semantics are
stable, the backing store can be replaced or expanded.
## First Implementation Slice
1. Service scaffold using the existing loader, validator, and graph export
model.
2. Repository registration endpoint with repo slug, URL, default branch, and
optional State Hub repo id.
3. Snapshot ingest endpoint that validates a `FabricGraphExport` and stores it
atomically.
4. Query endpoints for providers, consumers, unresolved dependencies, dependency
paths, and blast radius.
5. State Hub export endpoint matching `docs/state-hub-integration.md`.
6. Contract attachment for OpenAPI and AsyncAPI documents.
7. CycloneDX SBOM attachment for library/package inventory.
8. CloudEvents-style registry events once mutation endpoints exist.
## Open Design Questions
- Should the registry pull repos itself, or should repos/agents push validated
exports from CI? Push is simpler and keeps credentials narrower.
- Should repository registration live first in State Hub and sync into Fabric
Registry, or should Fabric Registry own its own repo registry and annotate
State Hub ids? The current boundary suggests Fabric Registry owns graph
registration, State Hub owns planning/coordination.
- Should the first storage backend be SQLite for local operations or Postgres
from the start? SQLite is enough for proving semantics; Postgres is better
once multiple agents write concurrently.
- Should xRegistry compatibility be a projection only, or should the internal
registry model follow xRegistry group/resource/version terminology? Start as
projection; revisit after the first API is exercised.

54
docs/first-rollout.md Normal file
View File

@@ -0,0 +1,54 @@
# First Rollout
The first rollout is represented by the seed declarations under `fabric/`.
Those files are intentionally centralized in Railiance Fabric for bootstrap;
the long-term target is for each owning repo to carry its own `fabric/`
declarations.
## Seeded Repos
| Repo | Seeded Service(s) | First Capability |
|------|-------------------|------------------|
| `railiance-platform` | OpenBao, CNPG, Valkey | runtime secrets, PostgreSQL, Redis-compatible cache |
| `net-kingdom` | IAM Profile contract | IAM Profile issuer |
| `key-cape` | IAM Profile API | IAM Profile issuer implementation |
| `flex-auth` | flex-auth API, Topaz | authorization decisions |
| `artifact-store` | object storage service | object storage, credential vending |
| `repo-scoping` | scope generator | scope generation |
| `the-custodian` | State Hub | coordination read model |
## Promotion Path
For each owning repo:
1. Copy the matching seed files from `railiance-fabric/fabric/` into the owning
repo's own `fabric/` directory.
2. Replace seed source links with repo-local source links.
3. Validate the owning repo by itself.
4. Validate the owning repo together with `railiance-fabric` and other
providers/consumers it depends on.
5. Export the multi-repo graph for State Hub ingestion.
6. Once repo-local declarations are authoritative, remove or mark the central
seed declarations as bootstrap-only.
## Suggested Order
1. `railiance-platform`: owns OpenBao, CNPG, and Valkey provider declarations.
2. `key-cape`: owns the first concrete IAM Profile implementation.
3. `flex-auth`: owns authorization decisions and concrete consumers of OpenBao
and IAM Profile capabilities.
4. `the-custodian/state-hub`: owns coordination read-model declarations and is
the first export consumer.
5. `repo-scoping`: owns scope-generation provider declarations.
6. `artifact-store`: can promote planned object-storage declarations when its
interfaces stabilize.
## Completion Signal
The rollout is good enough for the next phase when:
- each repo can validate its own declarations
- the combined graph has no unresolved dependencies
- State Hub can ingest a `FabricGraphExport`
- dashboard/search views can answer provider, consumer, unresolved, and blast
radius questions from the ingested graph

View File

@@ -0,0 +1,159 @@
# State Hub Integration Contract
Railiance Fabric is the authoring and validation layer for ecosystem graph
declarations. State Hub should ingest Fabric outputs as a read model for
coordination, search, dashboards, and planning. It should not become the
primary authoring surface for services, capabilities, interfaces, dependencies,
or bindings.
## Source-Of-Truth Boundary
| Layer | Owns | Does Not Own |
|-------|------|--------------|
| Participating repos | Declaration files under `fabric/` | Global graph interpretation |
| Railiance Fabric | Schemas, type catalogs, validation, graph construction, exports | State Hub tasks/progress/decisions |
| State Hub | Read-model storage, links to repos/workstreams/tasks/progress, dashboard/search views | Editing Fabric declarations |
The flow is:
```text
repo-local fabric/*.yaml
|
v
railiance-fabric validate/export
|
v
State Hub graph read model
|
v
dashboard, search, planning, progress links
```
## Export Shape
The CLI emits `FabricGraphExport` JSON:
```bash
railiance-fabric export --format json
```
Schema: `schemas/state-hub-export.schema.yaml`
Top-level shape:
```yaml
apiVersion: railiance.fabric/v1alpha1
kind: FabricGraphExport
nodes: []
edges: []
```
Node fields:
| Field | Meaning |
|-------|---------|
| `id` | Stable graph id from declaration metadata. |
| `kind` | Declaration kind: service, capability, interface, dependency, or binding. |
| `name` | Human-readable name. |
| `repo` | Owning repo slug. |
| `domain` | Owning domain slug. |
| `lifecycle` | Declaration lifecycle. |
Edge fields:
| Field | Meaning |
|-------|---------|
| `from` | Source node id. |
| `to` | Target node id. |
| `type` | Relationship type, such as `provides`, `exposes`, `available_via`, `consumes`, `binds:exact`, or `uses_interface`. |
## Proposed State Hub Read Model
Add a State Hub ingestion endpoint or job that stores the latest graph export
per source repo:
```text
POST /fabric/graph-exports
```
Suggested payload:
```json
{
"repo_slug": "railiance-fabric",
"commit": "<git-sha>",
"generated_at": "2026-05-17T00:00:00Z",
"graph": {
"apiVersion": "railiance.fabric/v1alpha1",
"kind": "FabricGraphExport",
"nodes": [],
"edges": []
}
}
```
Suggested storage:
```text
fabric_graph_exports
id
repo_id
commit
generated_at
graph_json
created_at
fabric_graph_nodes
export_id
graph_id
kind
name
repo_slug
domain_slug
lifecycle
fabric_graph_edges
export_id
from_graph_id
to_graph_id
edge_type
```
The normalized node/edge tables are optional at first. State Hub can begin with
`fabric_graph_exports.graph_json` and materialize node/edge tables once query
needs harden.
## Linking To Existing State Hub Entities
State Hub should enrich graph nodes by matching:
- `node.repo` -> `managed_repos.slug`
- `node.domain` -> `domains.slug`
- workplan source links -> `workstreams.slug` or file-backed workplan index
- progress events -> `repo_id` and related workstream/task when available
These links are annotations on the read model. They should never overwrite the
repo-owned declaration files.
## Ingestion Rules
1. Reject exports that fail `schemas/state-hub-export.schema.yaml`.
2. Record the source repo and commit for every accepted export.
3. Replace the previous latest export for the same repo only after the new
export validates.
4. Preserve historical exports long enough to compare graph drift.
5. Surface validation errors as State Hub progress events or human-review tasks,
but do not auto-edit declaration files.
## Initial Dashboard Queries
State Hub should be able to answer:
- providers for a capability type
- consumers of a capability or interface
- unresolved dependencies
- blast radius for an interface id or type
- graph nodes by repo/domain/lifecycle
These are the same query families exposed locally by Railiance Fabric. The hub
read model should match local answers for the same export.

68
docs/type-catalog.md Normal file
View File

@@ -0,0 +1,68 @@
# Core Type Catalog
The type catalog names the stable semantic capabilities and concrete
integration surfaces used by Railiance Fabric declarations.
The catalog has two jobs:
- prevent ad hoc strings in repo-owned declaration files
- give the future validator enough metadata to warn about mismatched
capability/interface combinations
Machine-readable catalog files:
- `catalog/capability-types.yaml`
- `catalog/interface-types.yaml`
## Capability Types
| Type | Lifecycle | Default Criticality | Default Data | Expected Interfaces |
|------|-----------|---------------------|--------------|---------------------|
| `runtime-secrets` | active | critical | secret | `openbao-kv-v2-mount`, `kubernetes-secret` |
| `iam-profile-issuer` | active | critical | restricted | `oidc-discovery`, `http-api` |
| `authorization-decision-service` | active | critical | restricted | `http-api`, `policy-package` |
| `postgresql-database-service` | active | high | confidential | `database-connection`, `openbao-dynamic-credential-role` |
| `redis-compatible-cache` | active | medium | internal | `database-connection`, `kubernetes-secret` |
| `object-storage` | planned | high | confidential | `object-storage-bucket`, `http-api` |
| `object-storage-credential-vending` | planned | high | secret | `http-api`, `openbao-dynamic-credential-role`, `sts-token` |
| `audit-event-sink` | planned | high | confidential | `event-stream`, `http-api` |
| `scope-generation` | active | medium | internal | `cli`, `http-api` |
| `coordination-read-model` | active | high | internal | `http-api`, `event-stream` |
## Interface Types
| Type | Lifecycle | Category | Typical Auth |
|------|-----------|----------|--------------|
| `http-api` | active | api | `none`, `oidc`, `jwt`, `mtls`, `api_key` |
| `oidc-discovery` | active | identity | `none` |
| `kubernetes-secret` | active | kubernetes | `kubernetes_service_account` |
| `kubernetes-crd` | active | kubernetes | `kubernetes_service_account` |
| `helm-release` | active | deployment | `kubernetes_service_account` |
| `cli` | active | tooling | `none`, `oidc`, `api_key`, `unknown` |
| `database-connection` | active | data | `database_role`, `static_secret`, `openbao_token` |
| `object-storage-bucket` | planned | storage | `sts_token`, `static_secret`, `openbao_token` |
| `event-stream` | planned | events | `jwt`, `mtls`, `api_key`, `unknown` |
| `policy-package` | active | policy | `none`, `oidc`, `jwt` |
| `openbao-kv-v2-mount` | active | secrets | `kubernetes_service_account`, `openbao_token` |
| `openbao-dynamic-credential-role` | active | credentials | `kubernetes_service_account`, `openbao_token` |
| `sts-token` | planned | credentials | `oidc`, `jwt`, `mtls` |
## Validation Rules For T05
The graph validator should initially enforce:
- every `CapabilityDeclaration.spec.capability_type` exists in
`capability-types.yaml`
- every `InterfaceDeclaration.spec.interface_type` exists in
`interface-types.yaml`
- every `DependencyDeclaration.spec.requires.capability_type` exists in
`capability-types.yaml`
- every `DependencyDeclaration.spec.interface.type`, when present, exists in
`interface-types.yaml`
- provider interface types should be among the capability type's
`expected_interface_types`, unless a declaration includes a documented
compatibility note
The validator should warn, not fail, when a declaration uses a planned type in
an active production dependency. That keeps early adoption possible while still
surfacing rollout risk.

40
docs/validator.md Normal file
View File

@@ -0,0 +1,40 @@
# Validator
The first validator entry point is:
```bash
railiance-fabric validate <repo-or-file>...
```
It accepts:
- a repo root containing `fabric/`
- a `fabric/` directory
- one or more declaration YAML files
The validator currently checks:
- YAML load errors
- declaration schema conformance
- duplicate graph IDs
- unknown capability and interface types
- missing service, capability, interface, dependency, and binding references
- missing provider capabilities for dependencies
- binding provider/dependency capability type mismatches
- active production dependencies without `metadata.source_links`
- active production dependencies whose providers do not cover the environment
- service dependency cycles from binding assertions
Exit behavior:
- exits `0` when there are no errors
- exits `1` when errors are present
- exits `1` for warnings only when `--warnings-as-errors` is used
Examples:
```bash
PYTHONPATH=. python -m railiance_fabric.cli validate .
PYTHONPATH=. python -m railiance_fabric.cli validate fabric/
PYTHONPATH=. python -m railiance_fabric.cli validate examples/declarations/invalid/*.yaml
```