generated from coulomb/repo-seed
Establish Railiance Fabric graph model
This commit is contained in:
40
.custodian-brief.md
Normal file
40
.custodian-brief.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Railiance Fabric Brief
|
||||||
|
|
||||||
|
Domain: railiance
|
||||||
|
Repo slug: railiance-fabric
|
||||||
|
State Hub topic ID: ca369340-a64e-442e-98f1-a4fa7dc74a38
|
||||||
|
State Hub workstream ID: bd190990-8e68-49a3-9ce4-0ba89103ea54
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Railiance Fabric defines the repo-owned declaration model for the Railiance
|
||||||
|
ecosystem graph: services, capabilities, interfaces, dependencies, bindings,
|
||||||
|
validation, discovery queries, and State Hub export contracts.
|
||||||
|
|
||||||
|
## Current Work
|
||||||
|
|
||||||
|
- `RAIL-FAB-WP-0001` is active and establishes the first ecosystem graph model.
|
||||||
|
- `T01` is done: `INTENT.md` defines the vocabulary and source-of-truth
|
||||||
|
boundary.
|
||||||
|
- `T02` is done: `docs/declaration-schema.md`, `schemas/`, and
|
||||||
|
`examples/declarations/` define the first declaration schema baseline.
|
||||||
|
- `T03` is done: `catalog/` and `docs/type-catalog.md` define the first
|
||||||
|
capability/interface type catalog.
|
||||||
|
- `T04` is done: `fabric/` contains seed declarations for the first Railiance
|
||||||
|
ecosystem provider/consumer graph.
|
||||||
|
- `T05` is done: `railiance-fabric validate` loads and validates schema,
|
||||||
|
catalog, reference, provider, source-link, and cycle checks.
|
||||||
|
- `T06` is done: discovery queries and JSON/Mermaid exports are available from
|
||||||
|
the CLI.
|
||||||
|
- `T07` is done: `docs/state-hub-integration.md` defines the graph export and
|
||||||
|
proposed hub read-model ingestion path.
|
||||||
|
- `T08` is done: `docs/adoption-guide.md` and `docs/first-rollout.md` define
|
||||||
|
how other repos should adopt and promote the seed declarations.
|
||||||
|
- All tasks in `RAIL-FAB-WP-0001` are done.
|
||||||
|
|
||||||
|
## State Hub
|
||||||
|
|
||||||
|
- Local API: `http://127.0.0.1:8000`
|
||||||
|
- Remote tunnel: `http://127.0.0.1:18000`
|
||||||
|
- After changing workplan files, sync from `~/the-custodian/state-hub` with:
|
||||||
|
`make fix-consistency REPO=railiance-fabric`
|
||||||
158
AGENTS.md
Normal file
158
AGENTS.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# railiance-fabric — Agent Instructions
|
||||||
|
|
||||||
|
## Repo Identity
|
||||||
|
|
||||||
|
**Purpose:** Railiance Fabric defines the repo-owned declaration model, validation tooling, graph queries, and State Hub export contract for the Railiance ecosystem graph.
|
||||||
|
|
||||||
|
**Domain:** railiance
|
||||||
|
**Repo slug:** railiance-fabric
|
||||||
|
**Topic ID:** `ca369340-a64e-442e-98f1-a4fa7dc74a38`
|
||||||
|
**Workplan prefix:** `RAIL-FAB-WP-`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State Hub Integration
|
||||||
|
|
||||||
|
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
|
||||||
|
there is no MCP server for Codex agents.
|
||||||
|
|
||||||
|
| Context | URL |
|
||||||
|
|---------|-----|
|
||||||
|
| Local workstation | `http://127.0.0.1:8000` |
|
||||||
|
| Remote via tunnel | `http://127.0.0.1:18000` |
|
||||||
|
|
||||||
|
### Orient at session start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Offline brief — works without hub connection
|
||||||
|
cat .custodian-brief.md
|
||||||
|
|
||||||
|
# Active workstreams for this domain
|
||||||
|
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=ca369340-a64e-442e-98f1-a4fa7dc74a38&status=active" \
|
||||||
|
| python3 -m json.tool
|
||||||
|
|
||||||
|
# Check inbox
|
||||||
|
curl -s "http://127.0.0.1:8000/messages/?to_agent=railiance-fabric&unread_only=true" \
|
||||||
|
| python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Mark a message read:
|
||||||
|
```bash
|
||||||
|
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||||
|
-H "Content-Type: application/json" -d '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log progress (required at session close)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"summary": "what was done",
|
||||||
|
"event_type": "note",
|
||||||
|
"author": "codex",
|
||||||
|
"workstream_id": "<uuid>",
|
||||||
|
"task_id": "<uuid>"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Omit `workstream_id` / `task_id` when not applicable.
|
||||||
|
|
||||||
|
### Update task status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"status": "in_progress"}'
|
||||||
|
# values: todo | in_progress | done | blocked
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flag a task for human review
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"needs_human": true, "intervention_note": "reason"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Protocol
|
||||||
|
|
||||||
|
**Start:**
|
||||||
|
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
|
||||||
|
2. Check inbox: `GET /messages/?to_agent=railiance-fabric&unread_only=true`; mark read
|
||||||
|
3. Scan workplans: `ls workplans/` — note `status: active` files and open tasks
|
||||||
|
4. Check blocked tasks: `GET /tasks/?needs_human=true`
|
||||||
|
|
||||||
|
**During work:**
|
||||||
|
- Update task statuses in workplan files as tasks progress
|
||||||
|
- Record significant decisions via `POST /decisions/`
|
||||||
|
|
||||||
|
**Close:**
|
||||||
|
1. Update workplan file task statuses to reflect progress
|
||||||
|
2. Log: `POST /progress/` with a summary of what changed
|
||||||
|
3. Note for the custodian operator: after workplan file changes, run from
|
||||||
|
`~/the-custodian/state-hub`:
|
||||||
|
```bash
|
||||||
|
make fix-consistency REPO=railiance-fabric
|
||||||
|
```
|
||||||
|
This syncs task status from files into the hub DB.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
|
Work items originate as files in this repo — not in the hub. The hub is a
|
||||||
|
read/cache/index layer that rebuilds from files.
|
||||||
|
|
||||||
|
**File location:** `workplans/RAIL-FAB-WP-NNNN-<slug>.md`
|
||||||
|
|
||||||
|
**Archived location:** completed workplans may move to
|
||||||
|
`workplans/archived/YYMMDD-RAIL-FAB-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
|
||||||
|
the completion/archive date; the frontmatter `id` does not change.
|
||||||
|
|
||||||
|
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
||||||
|
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
|
||||||
|
this only for low-risk work completed directly; create a normal workplan for
|
||||||
|
anything needing analysis, design, approval, dependencies, or multiple phases.
|
||||||
|
|
||||||
|
**Frontmatter:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
id: RAIL-FAB-WP-NNNN
|
||||||
|
type: workplan
|
||||||
|
title: "..."
|
||||||
|
domain: railiance
|
||||||
|
repo: railiance-fabric
|
||||||
|
status: active | done
|
||||||
|
owner: codex
|
||||||
|
topic_slug: ...
|
||||||
|
created: "YYYY-MM-DD"
|
||||||
|
updated: "YYYY-MM-DD"
|
||||||
|
state_hub_workstream_id: "<uuid>" # written by fix-consistency — do not edit
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
**Task block format** (one per `##` section):
|
||||||
|
|
||||||
|
```
|
||||||
|
## Task Title
|
||||||
|
|
||||||
|
` ` `task
|
||||||
|
id: RAIL-FAB-WP-NNNN-T01
|
||||||
|
status: todo | in_progress | done | blocked
|
||||||
|
priority: high | medium | low
|
||||||
|
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||||
|
` ` `
|
||||||
|
|
||||||
|
Task description text.
|
||||||
|
```
|
||||||
|
|
||||||
|
Status progression: `todo` → `in_progress` → `done` (or `blocked`)
|
||||||
|
|
||||||
|
To create a new workplan:
|
||||||
|
1. Write the file following the format above
|
||||||
|
2. Notify the custodian operator to run `make fix-consistency REPO=railiance-fabric`
|
||||||
|
(or send a message to the hub agent via `POST /messages/`)
|
||||||
232
INTENT.md
Normal file
232
INTENT.md
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
# Railiance Fabric Intent
|
||||||
|
|
||||||
|
Date: 2026-05-17
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
Railiance Fabric exists to make the Railiance ecosystem understandable,
|
||||||
|
discoverable, and evolvable as services begin to depend on one another.
|
||||||
|
|
||||||
|
It models the living fabric of repositories, services, capabilities,
|
||||||
|
interfaces, and dependencies so humans and agents can answer questions like:
|
||||||
|
|
||||||
|
- Which service provides runtime secrets?
|
||||||
|
- Which repos consume the NetKingdom IAM Profile?
|
||||||
|
- What breaks if the flex-auth decision envelope changes?
|
||||||
|
- Which workloads require OpenBao KV, dynamic database credentials, or
|
||||||
|
object-storage credential vending?
|
||||||
|
- Which dependencies are declared, missing, stale, or boundary-violating?
|
||||||
|
|
||||||
|
The core idea is simple:
|
||||||
|
|
||||||
|
```text
|
||||||
|
repo -> service -> capability -> interface -> dependency
|
||||||
|
```
|
||||||
|
|
||||||
|
Repos remain the source of truth for what they provide and require. Railiance
|
||||||
|
Fabric gives those declarations a shared schema, validation model, graph view,
|
||||||
|
and discovery surface.
|
||||||
|
|
||||||
|
## Why This Exists
|
||||||
|
|
||||||
|
Railiance is entering the phase where platform services, identity services,
|
||||||
|
application workloads, automation, policy engines, storage, and observability
|
||||||
|
will interact continuously.
|
||||||
|
|
||||||
|
Without an explicit ecosystem graph, those interactions become folklore:
|
||||||
|
implicit dependencies, stale mental maps, fragile deployment order, and unclear
|
||||||
|
ownership when interfaces change.
|
||||||
|
|
||||||
|
Railiance Fabric turns that implicit web into a reviewable graph:
|
||||||
|
|
||||||
|
- capabilities are discoverable by name and semantics
|
||||||
|
- interfaces are typed and versioned
|
||||||
|
- consumers declare their requirements
|
||||||
|
- providers declare what they actually offer
|
||||||
|
- State Hub can ingest the graph as a read model
|
||||||
|
- agents can reason about blast radius, missing providers, and safe sequencing
|
||||||
|
|
||||||
|
## Responsibility Boundary
|
||||||
|
|
||||||
|
### Repositories Own Declarations
|
||||||
|
|
||||||
|
Each repo owns file-backed declarations for its provided capabilities,
|
||||||
|
consumed capabilities, services, and interface contracts.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fabric/capabilities/*.yaml
|
||||||
|
fabric/dependencies/*.yaml
|
||||||
|
fabric/interfaces/*.yaml
|
||||||
|
fabric/services/*.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
These files are reviewable, versioned with the repo, and changed through normal
|
||||||
|
pull-request or workplan flow.
|
||||||
|
|
||||||
|
### Railiance Fabric Owns The Graph Model
|
||||||
|
|
||||||
|
Railiance Fabric owns:
|
||||||
|
|
||||||
|
- declaration schemas
|
||||||
|
- validation rules
|
||||||
|
- graph construction
|
||||||
|
- local inspection tools
|
||||||
|
- provider/consumer matching
|
||||||
|
- compatibility and drift checks
|
||||||
|
- example declarations for core Railiance services
|
||||||
|
- export formats for State Hub, docs, and dashboards
|
||||||
|
|
||||||
|
### State Hub Owns The Read Model
|
||||||
|
|
||||||
|
State Hub should ingest the ecosystem graph and expose it for coordination,
|
||||||
|
but it should not become the primary authoring surface for capability and
|
||||||
|
dependency declarations.
|
||||||
|
|
||||||
|
This keeps ADR-001 intact: formal work and declarations originate in repos;
|
||||||
|
the hub reads, visualizes, and coordinates.
|
||||||
|
|
||||||
|
## First-Class Concepts
|
||||||
|
|
||||||
|
### Repository
|
||||||
|
|
||||||
|
A source-controlled project with ownership, workplans, implementation, and
|
||||||
|
local declarations.
|
||||||
|
|
||||||
|
### Service
|
||||||
|
|
||||||
|
A deployable or callable unit produced by a repository. A repo may produce zero
|
||||||
|
or more services.
|
||||||
|
|
||||||
|
Examples: OpenBao, key-cape, flex-auth API, Topaz deployment, artifact-store.
|
||||||
|
|
||||||
|
### Capability
|
||||||
|
|
||||||
|
A stable semantic ability that consumers can depend on without hard-coding the
|
||||||
|
current implementation.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- runtime secrets
|
||||||
|
- IAM Profile issuer
|
||||||
|
- authorization decision service
|
||||||
|
- PostgreSQL database service
|
||||||
|
- object-storage credential vending
|
||||||
|
- scope generation
|
||||||
|
|
||||||
|
### Interface
|
||||||
|
|
||||||
|
The concrete contract through which a capability is consumed.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- HTTP API
|
||||||
|
- OIDC discovery
|
||||||
|
- Kubernetes Secret
|
||||||
|
- Kubernetes CRD
|
||||||
|
- Helm release
|
||||||
|
- CLI
|
||||||
|
- database connection
|
||||||
|
- object-storage bucket
|
||||||
|
- event stream
|
||||||
|
- policy package
|
||||||
|
- OpenBao KV v2 mount
|
||||||
|
- OpenBao database dynamic credential role
|
||||||
|
|
||||||
|
### Dependency
|
||||||
|
|
||||||
|
A consumer's declared requirement for a capability or interface, including
|
||||||
|
version, environment, auth, data classification, criticality, and fallback
|
||||||
|
expectations.
|
||||||
|
|
||||||
|
### Binding
|
||||||
|
|
||||||
|
A resolved edge between a consumer dependency and a provider capability.
|
||||||
|
Bindings may be exact, compatible, degraded, missing, or disputed.
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
- Source of truth lives in repos.
|
||||||
|
- Capabilities are stable; implementations may move.
|
||||||
|
- Interfaces are typed, versioned, and testable.
|
||||||
|
- Dependencies are explicit requirements, not accidental imports.
|
||||||
|
- Discovery is graph search, not tribal memory.
|
||||||
|
- Validation should catch missing providers before deployment time.
|
||||||
|
- Compatibility should be machine-checkable where possible.
|
||||||
|
- Human-readable files matter; agents and humans must both be able to inspect
|
||||||
|
declarations without a running service.
|
||||||
|
- The model must support partial adoption. A repo can begin with one declared
|
||||||
|
capability or dependency and mature over time.
|
||||||
|
- The graph should reveal boundary violations without pretending to own every
|
||||||
|
domain's decisions.
|
||||||
|
|
||||||
|
## Strategic Role
|
||||||
|
|
||||||
|
Railiance Fabric sits between repository scoping, State Hub, and the Railiance
|
||||||
|
deployment stack.
|
||||||
|
|
||||||
|
```text
|
||||||
|
repo-local declarations
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Railiance Fabric schema and graph tools
|
||||||
|
|
|
||||||
|
+-- State Hub ingestion and coordination
|
||||||
|
+-- documentation and topology maps
|
||||||
|
+-- agent planning and blast-radius analysis
|
||||||
|
+-- deployment readiness checks
|
||||||
|
```
|
||||||
|
|
||||||
|
It complements:
|
||||||
|
|
||||||
|
- `repo-scoping`, which explains what a repo is useful for.
|
||||||
|
- `the-custodian/state-hub`, which coordinates domains, workstreams, tasks, and
|
||||||
|
progress.
|
||||||
|
- `railiance-platform`, which deploys shared S3 services such as OpenBao,
|
||||||
|
PostgreSQL, Valkey, and object storage.
|
||||||
|
- `net-kingdom`, which owns identity, credential, and security architecture.
|
||||||
|
- `flex-auth`, which owns authorization policy and decision semantics.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
Railiance Fabric is not:
|
||||||
|
|
||||||
|
- a deployment orchestrator
|
||||||
|
- a replacement for State Hub
|
||||||
|
- a replacement for SCOPE.md
|
||||||
|
- a service mesh
|
||||||
|
- a CMDB that must manually mirror everything
|
||||||
|
- an authorization engine
|
||||||
|
- a secret manager
|
||||||
|
- a package registry
|
||||||
|
|
||||||
|
It may inform those systems, but its job is the ecosystem graph and declaration
|
||||||
|
model.
|
||||||
|
|
||||||
|
## Early Questions
|
||||||
|
|
||||||
|
- What is the smallest declaration schema that is useful without becoming
|
||||||
|
ceremony?
|
||||||
|
- Which capability and interface types must exist on day one?
|
||||||
|
- How should provider/consumer matching handle environment, version, auth,
|
||||||
|
tenant, and data-class constraints?
|
||||||
|
- Which graph checks are advisory, and which should block deployment?
|
||||||
|
- How does State Hub ingest the graph without becoming the authoring source?
|
||||||
|
- How do we represent capabilities that are planned but not deployed yet?
|
||||||
|
|
||||||
|
## Maturity Target
|
||||||
|
|
||||||
|
A mature Railiance Fabric should let a human or agent ask:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Show all consumers of OpenBao.
|
||||||
|
Show missing providers for production Railiance.
|
||||||
|
Show every service that depends on NetKingdom identity claims.
|
||||||
|
Show all interfaces crossing from S3 platform services into S5 applications.
|
||||||
|
Show blast radius for changing flex-auth decision envelope v1.
|
||||||
|
Show runtime readiness for tenant:coulomb onboarding.
|
||||||
|
```
|
||||||
|
|
||||||
|
and receive source-linked, repo-owned answers.
|
||||||
|
|
||||||
52
README.md
52
README.md
@@ -1,3 +1,51 @@
|
|||||||
# repo-seed
|
# Railiance Fabric
|
||||||
|
|
||||||
A git repository template to bootstrap coulomb projects from.
|
Railiance Fabric defines the repo-owned declaration model for the Railiance
|
||||||
|
ecosystem graph.
|
||||||
|
|
||||||
|
It will hold schemas, seed declarations, validation tools, graph queries, and
|
||||||
|
State Hub export contracts for services, capabilities, interfaces,
|
||||||
|
dependencies, and bindings across Railiance repositories.
|
||||||
|
|
||||||
|
## Validate Declarations
|
||||||
|
|
||||||
|
From a checkout with the Python dependencies installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
railiance-fabric validate .
|
||||||
|
```
|
||||||
|
|
||||||
|
During early bootstrapping, the local module entry point works too:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=. python -m railiance_fabric.cli validate .
|
||||||
|
```
|
||||||
|
|
||||||
|
The validator loads `fabric/` declarations, checks schema conformance, verifies
|
||||||
|
catalog type names, catches missing references/providers, checks active
|
||||||
|
production dependency source links, and warns about dependency cycles.
|
||||||
|
|
||||||
|
## Query The Graph
|
||||||
|
|
||||||
|
```bash
|
||||||
|
railiance-fabric providers runtime-secrets
|
||||||
|
railiance-fabric consumers railiance-platform.openbao.kv-v2
|
||||||
|
railiance-fabric dependency-path flex-auth.api
|
||||||
|
railiance-fabric unresolved
|
||||||
|
railiance-fabric blast-radius openbao-kv-v2-mount
|
||||||
|
railiance-fabric export --format json
|
||||||
|
railiance-fabric export --format mermaid
|
||||||
|
```
|
||||||
|
|
||||||
|
See `docs/discovery-queries.md` for command details.
|
||||||
|
|
||||||
|
## Adopt In Another Repo
|
||||||
|
|
||||||
|
See `docs/adoption-guide.md` for the declaration workflow and
|
||||||
|
`docs/first-rollout.md` for the initial Railiance repo rollout.
|
||||||
|
|
||||||
|
## Next: Ecosystem Registry Service
|
||||||
|
|
||||||
|
See `docs/ecosystem-registry-service.md` for the standards comparison and
|
||||||
|
service direction for registering repos and interacting with the combined
|
||||||
|
ecosystem model.
|
||||||
|
|||||||
139
SCOPE.md
Normal file
139
SCOPE.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# SCOPE
|
||||||
|
|
||||||
|
> This file helps you quickly understand what this repository is about,
|
||||||
|
> when it is relevant, and when it is not.
|
||||||
|
> It is intentionally lightweight and may be incomplete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## One-liner
|
||||||
|
|
||||||
|
Defines the Railiance ecosystem graph model so repos can declare services,
|
||||||
|
capabilities, interfaces, dependencies, and bindings in source-controlled files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Idea
|
||||||
|
|
||||||
|
Railiance Fabric turns implicit cross-repo dependencies into a reviewable graph.
|
||||||
|
Participating repos remain the source of truth for what they provide and
|
||||||
|
consume; this repo owns the shared schema, validation rules, graph construction,
|
||||||
|
query tooling, seed examples, and export format that State Hub can ingest as a
|
||||||
|
read model.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- YAML declaration schemas for services, capabilities, interfaces,
|
||||||
|
dependencies, and binding assertions.
|
||||||
|
- Capability and interface type catalogs for the Railiance ecosystem.
|
||||||
|
- Seed declarations for core Railiance providers and consumers.
|
||||||
|
- Local graph loading, validation, discovery queries, and export tooling.
|
||||||
|
- State Hub ingestion contract for graph exports.
|
||||||
|
- Adoption guidance for adding declarations to other repos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Deployment orchestration or GitOps ownership.
|
||||||
|
- Replacing State Hub workstreams, tasks, decisions, or progress events.
|
||||||
|
- Replacing repo-scoping, SCOPE.md, ADRs, service meshes, secret managers, or
|
||||||
|
authorization engines.
|
||||||
|
- Making State Hub the authoring surface for capability declarations.
|
||||||
|
- Runtime traffic discovery that bypasses repo-owned declarations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relevant When
|
||||||
|
|
||||||
|
- You need to declare what a Railiance repo provides or consumes.
|
||||||
|
- You need to ask which services depend on a capability or interface.
|
||||||
|
- You need to validate missing providers, stale interfaces, or compatibility
|
||||||
|
issues before sequencing work.
|
||||||
|
- You need a graph export that State Hub can display without owning the source
|
||||||
|
declarations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Not Relevant When
|
||||||
|
|
||||||
|
- You are deploying infrastructure rather than modeling its ecosystem contract.
|
||||||
|
- You need to manage State Hub tasks, decisions, messages, or progress events.
|
||||||
|
- You need service-specific implementation details owned by another repo.
|
||||||
|
- You need an authorization, secret-management, or package-registry runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
- Status: active planning
|
||||||
|
- Implementation: intent and first workplan present
|
||||||
|
- Stability: evolving
|
||||||
|
- Usage: internal Railiance ecosystem modeling
|
||||||
|
|
||||||
|
The first workplan is `RAIL-FAB-WP-0001`, which establishes vocabulary, schema,
|
||||||
|
seed examples, validator/query tooling, and State Hub integration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Fits
|
||||||
|
|
||||||
|
- Upstream dependencies: repo-owned declarations in participating Railiance
|
||||||
|
repos.
|
||||||
|
- Downstream consumers: State Hub read model, documentation, dashboards, and
|
||||||
|
agent planning flows.
|
||||||
|
- Often used with: `repo-scoping`, `the-custodian/state-hub`,
|
||||||
|
`railiance-platform`, `net-kingdom`, `flex-auth`, and `artifact-store`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Terminology
|
||||||
|
|
||||||
|
- Preferred terms: repository, service, capability, interface, dependency,
|
||||||
|
binding.
|
||||||
|
- Also known as: ecosystem graph, capability graph, dependency graph.
|
||||||
|
- Potentially confusing terms: State Hub is the read/cache/index layer here,
|
||||||
|
not the authoring source for declarations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related / Overlapping Repositories
|
||||||
|
|
||||||
|
- `repo-scoping` — explains what a repo is useful for; Railiance Fabric models
|
||||||
|
what repos provide and consume.
|
||||||
|
- `the-custodian/state-hub` — coordinates domains, workstreams, tasks, and
|
||||||
|
progress; it should ingest Fabric graph exports as a read model.
|
||||||
|
- `railiance-platform` — deploys shared platform services that should become
|
||||||
|
graph provider nodes.
|
||||||
|
- `net-kingdom` — owns identity, credential, and security architecture that
|
||||||
|
appears in capability/interface declarations.
|
||||||
|
- `flex-auth` — owns authorization policy and decision semantics that should be
|
||||||
|
represented as graph capabilities and interfaces.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Oriented
|
||||||
|
|
||||||
|
- Start with: `INTENT.md`
|
||||||
|
- Key files / directories: `workplans/`
|
||||||
|
- Entry points: `workplans/RAIL-FAB-WP-0001-ecosystem-graph-model.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Provided Capabilities
|
||||||
|
|
||||||
|
```capability
|
||||||
|
type: tooling
|
||||||
|
title: Railiance ecosystem graph model
|
||||||
|
description: Shared declaration schema, validation model, query tooling, and State Hub export contract for Railiance service/capability/dependency graphs.
|
||||||
|
keywords: [railiance, graph, capabilities, dependencies, interfaces, state-hub]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Repos own declarations. Railiance Fabric owns graph semantics and validation.
|
||||||
|
State Hub owns coordination and display of ingested read models.
|
||||||
11
catalog/README.md
Normal file
11
catalog/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Type Catalog
|
||||||
|
|
||||||
|
This directory contains the first Railiance Fabric type catalog.
|
||||||
|
|
||||||
|
- `capability-types.yaml` defines stable semantic capabilities.
|
||||||
|
- `interface-types.yaml` defines concrete integration surfaces.
|
||||||
|
|
||||||
|
Declaration schemas keep `capability_type` and `interface_type` as strings so
|
||||||
|
the core document shape stays decoupled from catalog evolution. The validator
|
||||||
|
planned in `RAIL-FAB-WP-0001-T05` should load these catalogs and reject unknown
|
||||||
|
types unless an explicit experimental override is added later.
|
||||||
121
catalog/capability-types.yaml
Normal file
121
catalog/capability-types.yaml
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityTypeCatalog
|
||||||
|
metadata:
|
||||||
|
id: railiance-fabric.capability-types
|
||||||
|
name: Railiance capability type catalog
|
||||||
|
owner: railiance-fabric
|
||||||
|
repo: railiance-fabric
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
version: v1alpha1
|
||||||
|
types:
|
||||||
|
- id: runtime-secrets
|
||||||
|
name: Runtime secrets
|
||||||
|
lifecycle: active
|
||||||
|
description: Stores or vends runtime secrets needed by workloads after deployment.
|
||||||
|
default_criticality: critical
|
||||||
|
default_data_classification: secret
|
||||||
|
expected_interface_types:
|
||||||
|
- openbao-kv-v2-mount
|
||||||
|
- kubernetes-secret
|
||||||
|
tags: [security, platform, secrets]
|
||||||
|
|
||||||
|
- id: iam-profile-issuer
|
||||||
|
name: IAM Profile issuer
|
||||||
|
lifecycle: active
|
||||||
|
description: Issues or serves identity profile claims used by Railiance services.
|
||||||
|
default_criticality: critical
|
||||||
|
default_data_classification: restricted
|
||||||
|
expected_interface_types:
|
||||||
|
- oidc-discovery
|
||||||
|
- http-api
|
||||||
|
tags: [identity, security]
|
||||||
|
|
||||||
|
- id: authorization-decision-service
|
||||||
|
name: Authorization decision service
|
||||||
|
lifecycle: active
|
||||||
|
description: Evaluates authorization policy and returns allow/deny decisions with context.
|
||||||
|
default_criticality: critical
|
||||||
|
default_data_classification: restricted
|
||||||
|
expected_interface_types:
|
||||||
|
- http-api
|
||||||
|
- policy-package
|
||||||
|
tags: [authorization, policy, security]
|
||||||
|
|
||||||
|
- id: postgresql-database-service
|
||||||
|
name: PostgreSQL database service
|
||||||
|
lifecycle: active
|
||||||
|
description: Provides PostgreSQL databases, roles, and connection endpoints for workloads.
|
||||||
|
default_criticality: high
|
||||||
|
default_data_classification: confidential
|
||||||
|
expected_interface_types:
|
||||||
|
- database-connection
|
||||||
|
- openbao-dynamic-credential-role
|
||||||
|
tags: [database, platform]
|
||||||
|
|
||||||
|
- id: redis-compatible-cache
|
||||||
|
name: Redis-compatible cache
|
||||||
|
lifecycle: active
|
||||||
|
description: Provides Redis protocol compatible caching or ephemeral data storage.
|
||||||
|
default_criticality: medium
|
||||||
|
default_data_classification: internal
|
||||||
|
expected_interface_types:
|
||||||
|
- database-connection
|
||||||
|
- kubernetes-secret
|
||||||
|
tags: [cache, platform]
|
||||||
|
|
||||||
|
- id: object-storage
|
||||||
|
name: Object storage
|
||||||
|
lifecycle: planned
|
||||||
|
description: Provides bucket-style durable object storage for Railiance workloads.
|
||||||
|
default_criticality: high
|
||||||
|
default_data_classification: confidential
|
||||||
|
expected_interface_types:
|
||||||
|
- object-storage-bucket
|
||||||
|
- http-api
|
||||||
|
tags: [storage, platform]
|
||||||
|
|
||||||
|
- id: object-storage-credential-vending
|
||||||
|
name: Object-storage credential vending
|
||||||
|
lifecycle: planned
|
||||||
|
description: Issues scoped temporary credentials for object-storage access.
|
||||||
|
default_criticality: high
|
||||||
|
default_data_classification: secret
|
||||||
|
expected_interface_types:
|
||||||
|
- http-api
|
||||||
|
- openbao-dynamic-credential-role
|
||||||
|
- sts-token
|
||||||
|
tags: [storage, credentials, security]
|
||||||
|
|
||||||
|
- id: audit-event-sink
|
||||||
|
name: Audit/event sink
|
||||||
|
lifecycle: planned
|
||||||
|
description: Accepts audit, operational, or domain events for durable recording or routing.
|
||||||
|
default_criticality: high
|
||||||
|
default_data_classification: confidential
|
||||||
|
expected_interface_types:
|
||||||
|
- event-stream
|
||||||
|
- http-api
|
||||||
|
tags: [events, audit, observability]
|
||||||
|
|
||||||
|
- id: scope-generation
|
||||||
|
name: Scope generation
|
||||||
|
lifecycle: active
|
||||||
|
description: Produces repo or project scope descriptions used by humans and agents.
|
||||||
|
default_criticality: medium
|
||||||
|
default_data_classification: internal
|
||||||
|
expected_interface_types:
|
||||||
|
- cli
|
||||||
|
- http-api
|
||||||
|
tags: [planning, agents, documentation]
|
||||||
|
|
||||||
|
- id: coordination-read-model
|
||||||
|
name: Coordination read model
|
||||||
|
lifecycle: active
|
||||||
|
description: Exposes coordination state for repos, workstreams, tasks, decisions, and progress.
|
||||||
|
default_criticality: high
|
||||||
|
default_data_classification: internal
|
||||||
|
expected_interface_types:
|
||||||
|
- http-api
|
||||||
|
- event-stream
|
||||||
|
tags: [coordination, state-hub, planning]
|
||||||
114
catalog/interface-types.yaml
Normal file
114
catalog/interface-types.yaml
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceTypeCatalog
|
||||||
|
metadata:
|
||||||
|
id: railiance-fabric.interface-types
|
||||||
|
name: Railiance interface type catalog
|
||||||
|
owner: railiance-fabric
|
||||||
|
repo: railiance-fabric
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
version: v1alpha1
|
||||||
|
types:
|
||||||
|
- id: http-api
|
||||||
|
name: HTTP API
|
||||||
|
lifecycle: active
|
||||||
|
description: Request/response HTTP interface, usually JSON over HTTPS.
|
||||||
|
category: api
|
||||||
|
typical_auth_methods: [none, oidc, jwt, mtls, api_key]
|
||||||
|
versioning: path, header, media-type, or documented semantic version.
|
||||||
|
|
||||||
|
- id: oidc-discovery
|
||||||
|
name: OIDC discovery
|
||||||
|
lifecycle: active
|
||||||
|
description: OpenID Connect discovery metadata and JWKS endpoints.
|
||||||
|
category: identity
|
||||||
|
typical_auth_methods: [none]
|
||||||
|
versioning: issuer URL and advertised metadata.
|
||||||
|
|
||||||
|
- id: kubernetes-secret
|
||||||
|
name: Kubernetes Secret
|
||||||
|
lifecycle: active
|
||||||
|
description: Kubernetes Secret object consumed by workloads in a namespace.
|
||||||
|
category: kubernetes
|
||||||
|
typical_auth_methods: [kubernetes_service_account]
|
||||||
|
versioning: object name, key schema, and owner annotations.
|
||||||
|
|
||||||
|
- id: kubernetes-crd
|
||||||
|
name: Kubernetes CRD
|
||||||
|
lifecycle: active
|
||||||
|
description: Kubernetes custom resource definition and versioned resource schema.
|
||||||
|
category: kubernetes
|
||||||
|
typical_auth_methods: [kubernetes_service_account]
|
||||||
|
versioning: group, version, and kind.
|
||||||
|
|
||||||
|
- id: helm-release
|
||||||
|
name: Helm release
|
||||||
|
lifecycle: active
|
||||||
|
description: Helm chart/release interface used to install or configure a service.
|
||||||
|
category: deployment
|
||||||
|
typical_auth_methods: [kubernetes_service_account]
|
||||||
|
versioning: chart version and values schema.
|
||||||
|
|
||||||
|
- id: cli
|
||||||
|
name: CLI
|
||||||
|
lifecycle: active
|
||||||
|
description: Command-line interface consumed by humans, agents, or automation.
|
||||||
|
category: tooling
|
||||||
|
typical_auth_methods: [none, oidc, api_key, unknown]
|
||||||
|
versioning: command version and documented flags.
|
||||||
|
|
||||||
|
- id: database-connection
|
||||||
|
name: Database connection
|
||||||
|
lifecycle: active
|
||||||
|
description: Network database endpoint plus credentials and connection parameters.
|
||||||
|
category: data
|
||||||
|
typical_auth_methods: [database_role, static_secret, openbao_token]
|
||||||
|
versioning: engine version, connection contract, and migration compatibility.
|
||||||
|
|
||||||
|
- id: object-storage-bucket
|
||||||
|
name: Object-storage bucket
|
||||||
|
lifecycle: planned
|
||||||
|
description: Bucket, prefix, policy, and endpoint contract for object storage.
|
||||||
|
category: storage
|
||||||
|
typical_auth_methods: [sts_token, static_secret, openbao_token]
|
||||||
|
versioning: bucket policy version and object layout contract.
|
||||||
|
|
||||||
|
- id: event-stream
|
||||||
|
name: Event stream
|
||||||
|
lifecycle: planned
|
||||||
|
description: Pub/sub or streaming interface for audit, operational, or domain events.
|
||||||
|
category: events
|
||||||
|
typical_auth_methods: [jwt, mtls, api_key, unknown]
|
||||||
|
versioning: subject/topic names and event envelope schema.
|
||||||
|
|
||||||
|
- id: policy-package
|
||||||
|
name: Policy package
|
||||||
|
lifecycle: active
|
||||||
|
description: Versioned policy bundle consumed by an authorization runtime.
|
||||||
|
category: policy
|
||||||
|
typical_auth_methods: [none, oidc, jwt]
|
||||||
|
versioning: package version and policy input/output schema.
|
||||||
|
|
||||||
|
- id: openbao-kv-v2-mount
|
||||||
|
name: OpenBao KV v2 mount
|
||||||
|
lifecycle: active
|
||||||
|
description: OpenBao KV v2 mount path and secret layout contract.
|
||||||
|
category: secrets
|
||||||
|
typical_auth_methods: [kubernetes_service_account, openbao_token]
|
||||||
|
versioning: mount path, key layout, and policy version.
|
||||||
|
|
||||||
|
- id: openbao-dynamic-credential-role
|
||||||
|
name: OpenBao dynamic credential role
|
||||||
|
lifecycle: active
|
||||||
|
description: OpenBao role that issues dynamic credentials for another service.
|
||||||
|
category: credentials
|
||||||
|
typical_auth_methods: [kubernetes_service_account, openbao_token]
|
||||||
|
versioning: role name, policy, lease semantics, and backend version.
|
||||||
|
|
||||||
|
- id: sts-token
|
||||||
|
name: STS token
|
||||||
|
lifecycle: planned
|
||||||
|
description: Temporary scoped credential issued for object storage or similar services.
|
||||||
|
category: credentials
|
||||||
|
typical_auth_methods: [oidc, jwt, mtls]
|
||||||
|
versioning: token claim schema, audience, and lease semantics.
|
||||||
207
docs/adoption-guide.md
Normal file
207
docs/adoption-guide.md
Normal 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
252
docs/declaration-schema.md
Normal 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
85
docs/discovery-queries.md
Normal 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`
|
||||||
200
docs/ecosystem-registry-service.md
Normal file
200
docs/ecosystem-registry-service.md
Normal 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
54
docs/first-rollout.md
Normal 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
|
||||||
159
docs/state-hub-integration.md
Normal file
159
docs/state-hub-integration.md
Normal 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
68
docs/type-catalog.md
Normal 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
40
docs/validator.md
Normal 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
|
||||||
|
```
|
||||||
15
examples/declarations/README.md
Normal file
15
examples/declarations/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Declaration Fixtures
|
||||||
|
|
||||||
|
These fixtures support the T02 schema baseline and give the future validator
|
||||||
|
real inputs to exercise.
|
||||||
|
|
||||||
|
`valid/` contains a coherent mini-graph:
|
||||||
|
|
||||||
|
- OpenBao service
|
||||||
|
- OpenBao runtime-secrets capability
|
||||||
|
- OpenBao KV v2 interface
|
||||||
|
- flex-auth runtime-secrets dependency
|
||||||
|
- binding assertion from flex-auth to OpenBao
|
||||||
|
|
||||||
|
`invalid/` contains schema-level failures. The future validator should report
|
||||||
|
clear errors for these before it attempts graph-level checks.
|
||||||
15
examples/declarations/invalid/binding-bad-status.yaml
Normal file
15
examples/declarations/invalid/binding-bad-status.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.bad-binding-status
|
||||||
|
name: Bad binding status
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev]
|
||||||
|
dependency_id: flex-auth.api.needs-runtime-secrets
|
||||||
|
provider_capability_id: railiance-platform.openbao.runtime-secrets
|
||||||
|
status: accepted
|
||||||
|
rationale: Invalid because accepted is not a binding status.
|
||||||
16
examples/declarations/invalid/capability-bad-lifecycle.yaml
Normal file
16
examples/declarations/invalid/capability-bad-lifecycle.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao.bad-lifecycle
|
||||||
|
name: Bad lifecycle capability
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: started
|
||||||
|
environments: [dev]
|
||||||
|
description: Invalid because lifecycle must use the shared enum.
|
||||||
|
capability_type: runtime-secrets
|
||||||
|
service_id: railiance-platform.openbao
|
||||||
|
criticality: high
|
||||||
|
data_classification: secret
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.bad-environment
|
||||||
|
name: Bad environment dependency
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [production]
|
||||||
|
consumer_service_id: flex-auth.api
|
||||||
|
requires:
|
||||||
|
capability_type: runtime-secrets
|
||||||
|
criticality: high
|
||||||
|
data_classification: secret
|
||||||
18
examples/declarations/invalid/interface-bad-auth.yaml
Normal file
18
examples/declarations/invalid/interface-bad-auth.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao.bad-auth
|
||||||
|
name: Bad auth interface
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev]
|
||||||
|
description: Invalid because oauth2 is not in the initial auth-method enum.
|
||||||
|
interface_type: http-api
|
||||||
|
version: v1
|
||||||
|
service_id: railiance-platform.openbao
|
||||||
|
auth:
|
||||||
|
method: oauth2
|
||||||
|
data_classification: internal
|
||||||
11
examples/declarations/invalid/service-missing-id.yaml
Normal file
11
examples/declarations/invalid/service-missing-id.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
name: Missing ID Service
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev]
|
||||||
|
description: Invalid because metadata.id is required.
|
||||||
23
examples/declarations/valid/binding-flex-auth-openbao.yaml
Normal file
23
examples/declarations/valid/binding-flex-auth-openbao.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.runtime-secrets-to-openbao
|
||||||
|
name: flex-auth runtime secrets binding
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: Runtime secrets binding note
|
||||||
|
path: docs/runtime-secrets.md
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
dependency_id: flex-auth.api.needs-runtime-secrets
|
||||||
|
provider_capability_id: railiance-platform.openbao.runtime-secrets
|
||||||
|
provider_interface_id: railiance-platform.openbao.kv-v2
|
||||||
|
status: exact
|
||||||
|
rationale: flex-auth uses the OpenBao KV v2 mount as its runtime secrets provider.
|
||||||
|
compatibility:
|
||||||
|
version: v1
|
||||||
|
compatible_with:
|
||||||
|
- railiance-platform.openbao.kv-v2
|
||||||
26
examples/declarations/valid/capability-runtime-secrets.yaml
Normal file
26
examples/declarations/valid/capability-runtime-secrets.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao.runtime-secrets
|
||||||
|
name: Runtime secrets
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: Runtime secrets workplan
|
||||||
|
path: workplans/RAIL-PLAT-WP-openbao-runtime-secrets.md
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Stores and serves workload runtime secrets through OpenBao.
|
||||||
|
capability_type: runtime-secrets
|
||||||
|
service_id: railiance-platform.openbao
|
||||||
|
interface_ids:
|
||||||
|
- railiance-platform.openbao.kv-v2
|
||||||
|
criticality: critical
|
||||||
|
data_classification: secret
|
||||||
|
compatibility:
|
||||||
|
version: v1
|
||||||
|
compatible_with:
|
||||||
|
- railiance-platform.openbao.kv-v2
|
||||||
|
notes: Initial runtime secrets capability for Railiance workloads.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.needs-runtime-secrets
|
||||||
|
name: flex-auth runtime secrets dependency
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: flex-auth deployment values
|
||||||
|
path: deploy/values.yaml
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
consumer_service_id: flex-auth.api
|
||||||
|
requires:
|
||||||
|
capability_type: runtime-secrets
|
||||||
|
interface:
|
||||||
|
type: openbao-kv-v2-mount
|
||||||
|
version_constraint: ">=v1 <v2"
|
||||||
|
auth:
|
||||||
|
method: kubernetes_service_account
|
||||||
|
audience: openbao
|
||||||
|
criticality: critical
|
||||||
|
data_classification: secret
|
||||||
|
fallback:
|
||||||
|
mode: none
|
||||||
|
description: flex-auth cannot start without runtime secrets.
|
||||||
|
compatibility:
|
||||||
|
requires:
|
||||||
|
- openbao-kv-v2-mount v1
|
||||||
29
examples/declarations/valid/interface-openbao-kv-v2.yaml
Normal file
29
examples/declarations/valid/interface-openbao-kv-v2.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao.kv-v2
|
||||||
|
name: OpenBao KV v2 mount
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: OpenBao KV mount manifest
|
||||||
|
path: manifests/openbao/kv-v2.yaml
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: KV v2 secret mount exposed to approved Railiance workloads.
|
||||||
|
interface_type: openbao-kv-v2-mount
|
||||||
|
version: v1
|
||||||
|
service_id: railiance-platform.openbao
|
||||||
|
capability_ids:
|
||||||
|
- railiance-platform.openbao.runtime-secrets
|
||||||
|
endpoint:
|
||||||
|
path: secret/data/railiance
|
||||||
|
auth:
|
||||||
|
method: kubernetes_service_account
|
||||||
|
audience: openbao
|
||||||
|
data_classification: secret
|
||||||
|
compatibility:
|
||||||
|
version: v1
|
||||||
|
notes: Consumers must use workload service-account authentication.
|
||||||
20
examples/declarations/valid/service-openbao.yaml
Normal file
20
examples/declarations/valid/service-openbao.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao
|
||||||
|
name: OpenBao
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: OpenBao platform chart
|
||||||
|
path: charts/openbao/values.yaml
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: OpenBao service used by Railiance workloads for runtime secrets.
|
||||||
|
service_type: platform-service
|
||||||
|
provides_capabilities:
|
||||||
|
- railiance-platform.openbao.runtime-secrets
|
||||||
|
exposes_interfaces:
|
||||||
|
- railiance-platform.openbao.kv-v2
|
||||||
20
fabric/README.md
Normal file
20
fabric/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Seed Declarations
|
||||||
|
|
||||||
|
These declarations are the first repo-local seed graph for Railiance Fabric.
|
||||||
|
|
||||||
|
They model current and planned Railiance ecosystem providers:
|
||||||
|
|
||||||
|
- OpenBao runtime secrets
|
||||||
|
- NetKingdom IAM Profile contract
|
||||||
|
- key-cape IAM Profile implementation
|
||||||
|
- flex-auth authorization decisions
|
||||||
|
- Topaz delegated PDP runtime
|
||||||
|
- CloudNativePG PostgreSQL
|
||||||
|
- Valkey Redis-compatible cache
|
||||||
|
- artifact-store object storage and credential vending
|
||||||
|
- State Hub coordination read model
|
||||||
|
- repo-scoping scope generation
|
||||||
|
|
||||||
|
The files are intentionally small. They are seed data for graph loading,
|
||||||
|
validation, and State Hub export work, not a claim that every upstream repo has
|
||||||
|
already adopted Fabric declarations as source of truth.
|
||||||
16
fabric/bindings/artifact-store-runtime-secrets-openbao.yaml
Normal file
16
fabric/bindings/artifact-store-runtime-secrets-openbao.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
id: artifact-store.object-storage.runtime-secrets-to-openbao
|
||||||
|
name: artifact-store runtime secrets binding
|
||||||
|
owner: artifact-store
|
||||||
|
repo: artifact-store
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: planned
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
dependency_id: artifact-store.object-storage.needs-runtime-secrets
|
||||||
|
provider_capability_id: railiance-platform.openbao.runtime-secrets
|
||||||
|
provider_interface_id: railiance-platform.openbao.kv-v2
|
||||||
|
status: compatible
|
||||||
|
rationale: Planned object-storage credential vending should use OpenBao for protected runtime secrets.
|
||||||
16
fabric/bindings/flex-auth-iam-profile-key-cape.yaml
Normal file
16
fabric/bindings/flex-auth-iam-profile-key-cape.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.iam-profile-to-key-cape
|
||||||
|
name: flex-auth IAM Profile binding
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
dependency_id: flex-auth.api.needs-iam-profile
|
||||||
|
provider_capability_id: key-cape.iam-profile.issuer
|
||||||
|
provider_interface_id: key-cape.iam-profile.oidc-discovery
|
||||||
|
status: compatible
|
||||||
|
rationale: key-cape is the lightweight IAM Profile implementation for Railiance workloads.
|
||||||
16
fabric/bindings/flex-auth-runtime-secrets-openbao.yaml
Normal file
16
fabric/bindings/flex-auth-runtime-secrets-openbao.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.runtime-secrets-to-openbao
|
||||||
|
name: flex-auth runtime secrets binding
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
dependency_id: flex-auth.api.needs-runtime-secrets
|
||||||
|
provider_capability_id: railiance-platform.openbao.runtime-secrets
|
||||||
|
provider_interface_id: railiance-platform.openbao.kv-v2
|
||||||
|
status: exact
|
||||||
|
rationale: flex-auth uses OpenBao KV v2 as the runtime secrets provider.
|
||||||
16
fabric/bindings/flex-auth-topaz-runtime.yaml
Normal file
16
fabric/bindings/flex-auth-topaz-runtime.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.topaz-runtime-binding
|
||||||
|
name: flex-auth Topaz runtime binding
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
dependency_id: flex-auth.api.needs-topaz-runtime
|
||||||
|
provider_capability_id: flex-auth.topaz.authorization-runtime
|
||||||
|
provider_interface_id: flex-auth.topaz.http-api
|
||||||
|
status: exact
|
||||||
|
rationale: flex-auth delegates PDP execution to its Topaz runtime.
|
||||||
16
fabric/bindings/state-hub-postgresql-cnpg.yaml
Normal file
16
fabric/bindings/state-hub-postgresql-cnpg.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
id: the-custodian.state-hub.postgresql-to-cnpg
|
||||||
|
name: State Hub PostgreSQL binding
|
||||||
|
owner: the-custodian
|
||||||
|
repo: the-custodian
|
||||||
|
domain: custodian
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
dependency_id: the-custodian.state-hub.needs-postgresql
|
||||||
|
provider_capability_id: railiance-platform.cnpg.postgresql
|
||||||
|
provider_interface_id: railiance-platform.cnpg.database-connection
|
||||||
|
status: compatible
|
||||||
|
rationale: State Hub persists coordination data in PostgreSQL, with CNPG as the Railiance database provider.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: artifact-store.object-storage.credentials
|
||||||
|
name: Object-storage credential vending
|
||||||
|
owner: artifact-store
|
||||||
|
repo: artifact-store
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: planned
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Planned scoped credential vending for object-storage access.
|
||||||
|
capability_type: object-storage-credential-vending
|
||||||
|
service_id: artifact-store.storage-service
|
||||||
|
interface_ids:
|
||||||
|
- artifact-store.object-storage.sts
|
||||||
|
criticality: high
|
||||||
|
data_classification: secret
|
||||||
18
fabric/capabilities/artifact-store-object-storage.yaml
Normal file
18
fabric/capabilities/artifact-store-object-storage.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: artifact-store.object-storage
|
||||||
|
name: Object storage
|
||||||
|
owner: artifact-store
|
||||||
|
repo: artifact-store
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: planned
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Planned object storage for artifacts and workload data.
|
||||||
|
capability_type: object-storage
|
||||||
|
service_id: artifact-store.storage-service
|
||||||
|
interface_ids:
|
||||||
|
- artifact-store.object-storage.bucket
|
||||||
|
criticality: high
|
||||||
|
data_classification: confidential
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.authorization-decisions
|
||||||
|
name: flex-auth authorization decisions
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Evaluates Railiance authorization requests and returns decision envelopes.
|
||||||
|
capability_type: authorization-decision-service
|
||||||
|
service_id: flex-auth.api
|
||||||
|
interface_ids:
|
||||||
|
- flex-auth.api.http-api
|
||||||
|
- flex-auth.api.policy-package
|
||||||
|
criticality: critical
|
||||||
|
data_classification: restricted
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.topaz.authorization-runtime
|
||||||
|
name: Topaz authorization runtime
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Delegated PDP runtime used for authorization policy evaluation.
|
||||||
|
capability_type: authorization-decision-service
|
||||||
|
service_id: flex-auth.topaz
|
||||||
|
interface_ids:
|
||||||
|
- flex-auth.topaz.http-api
|
||||||
|
criticality: critical
|
||||||
|
data_classification: restricted
|
||||||
19
fabric/capabilities/key-cape-iam-profile-issuer.yaml
Normal file
19
fabric/capabilities/key-cape-iam-profile-issuer.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: key-cape.iam-profile.issuer
|
||||||
|
name: key-cape IAM Profile issuer
|
||||||
|
owner: key-cape
|
||||||
|
repo: key-cape
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Implements IAM Profile claim serving for Railiance workloads.
|
||||||
|
capability_type: iam-profile-issuer
|
||||||
|
service_id: key-cape.iam-profile
|
||||||
|
interface_ids:
|
||||||
|
- key-cape.iam-profile.http-api
|
||||||
|
- key-cape.iam-profile.oidc-discovery
|
||||||
|
criticality: critical
|
||||||
|
data_classification: restricted
|
||||||
18
fabric/capabilities/net-kingdom-iam-profile-issuer.yaml
Normal file
18
fabric/capabilities/net-kingdom-iam-profile-issuer.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: net-kingdom.iam-profile.issuer
|
||||||
|
name: NetKingdom IAM Profile issuer
|
||||||
|
owner: net-kingdom
|
||||||
|
repo: net-kingdom
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: Defines the IAM Profile identity claims contract for Railiance.
|
||||||
|
capability_type: iam-profile-issuer
|
||||||
|
service_id: net-kingdom.iam-profile
|
||||||
|
interface_ids:
|
||||||
|
- net-kingdom.iam-profile.oidc-discovery
|
||||||
|
criticality: critical
|
||||||
|
data_classification: restricted
|
||||||
18
fabric/capabilities/railiance-platform-cnpg-postgresql.yaml
Normal file
18
fabric/capabilities/railiance-platform-cnpg-postgresql.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.cnpg.postgresql
|
||||||
|
name: CloudNativePG PostgreSQL
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Provides PostgreSQL databases and connection endpoints.
|
||||||
|
capability_type: postgresql-database-service
|
||||||
|
service_id: railiance-platform.cnpg
|
||||||
|
interface_ids:
|
||||||
|
- railiance-platform.cnpg.database-connection
|
||||||
|
criticality: high
|
||||||
|
data_classification: confidential
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao.runtime-secrets
|
||||||
|
name: Runtime secrets
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Stores and serves workload runtime secrets through OpenBao.
|
||||||
|
capability_type: runtime-secrets
|
||||||
|
service_id: railiance-platform.openbao
|
||||||
|
interface_ids:
|
||||||
|
- railiance-platform.openbao.kv-v2
|
||||||
|
criticality: critical
|
||||||
|
data_classification: secret
|
||||||
18
fabric/capabilities/railiance-platform-valkey-cache.yaml
Normal file
18
fabric/capabilities/railiance-platform-valkey-cache.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.valkey.cache
|
||||||
|
name: Valkey cache
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Provides Redis-compatible cache storage.
|
||||||
|
capability_type: redis-compatible-cache
|
||||||
|
service_id: railiance-platform.valkey
|
||||||
|
interface_ids:
|
||||||
|
- railiance-platform.valkey.database-connection
|
||||||
|
criticality: medium
|
||||||
|
data_classification: internal
|
||||||
18
fabric/capabilities/repo-scoping-scope-generation.yaml
Normal file
18
fabric/capabilities/repo-scoping-scope-generation.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: repo-scoping.scope-generation
|
||||||
|
name: Repo scope generation
|
||||||
|
owner: repo-scoping
|
||||||
|
repo: repo-scoping
|
||||||
|
domain: custodian
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: Generates repo scope, boundary, and usefulness descriptions.
|
||||||
|
capability_type: scope-generation
|
||||||
|
service_id: repo-scoping.scope-generator
|
||||||
|
interface_ids:
|
||||||
|
- repo-scoping.scope-generator.cli
|
||||||
|
criticality: medium
|
||||||
|
data_classification: internal
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
id: the-custodian.state-hub.coordination
|
||||||
|
name: State Hub coordination read model
|
||||||
|
owner: the-custodian
|
||||||
|
repo: the-custodian
|
||||||
|
domain: custodian
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: Exposes repo, workstream, task, decision, and progress state for coordination.
|
||||||
|
capability_type: coordination-read-model
|
||||||
|
service_id: the-custodian.state-hub
|
||||||
|
interface_ids:
|
||||||
|
- the-custodian.state-hub.http-api
|
||||||
|
criticality: high
|
||||||
|
data_classification: internal
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
id: artifact-store.object-storage.needs-runtime-secrets
|
||||||
|
name: artifact-store runtime secrets dependency
|
||||||
|
owner: artifact-store
|
||||||
|
repo: artifact-store
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: planned
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
consumer_service_id: artifact-store.storage-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: Credential vending needs a protected source for signing and backend secrets.
|
||||||
27
fabric/dependencies/flex-auth-api-iam-profile.yaml
Normal file
27
fabric/dependencies/flex-auth-api-iam-profile.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.needs-iam-profile
|
||||||
|
name: flex-auth IAM Profile dependency
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: Seed dependency declaration
|
||||||
|
path: fabric/README.md
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
consumer_service_id: flex-auth.api
|
||||||
|
requires:
|
||||||
|
capability_type: iam-profile-issuer
|
||||||
|
interface:
|
||||||
|
type: oidc-discovery
|
||||||
|
version_constraint: ">=v1 <v2"
|
||||||
|
auth:
|
||||||
|
method: none
|
||||||
|
criticality: critical
|
||||||
|
data_classification: restricted
|
||||||
|
fallback:
|
||||||
|
mode: none
|
||||||
|
description: Authorization decisions require trusted IAM Profile claims.
|
||||||
28
fabric/dependencies/flex-auth-api-runtime-secrets.yaml
Normal file
28
fabric/dependencies/flex-auth-api-runtime-secrets.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.needs-runtime-secrets
|
||||||
|
name: flex-auth runtime secrets dependency
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: Seed dependency declaration
|
||||||
|
path: fabric/README.md
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
consumer_service_id: flex-auth.api
|
||||||
|
requires:
|
||||||
|
capability_type: runtime-secrets
|
||||||
|
interface:
|
||||||
|
type: openbao-kv-v2-mount
|
||||||
|
version_constraint: ">=v1 <v2"
|
||||||
|
auth:
|
||||||
|
method: kubernetes_service_account
|
||||||
|
audience: openbao
|
||||||
|
criticality: critical
|
||||||
|
data_classification: secret
|
||||||
|
fallback:
|
||||||
|
mode: none
|
||||||
|
description: flex-auth cannot start without runtime secrets.
|
||||||
28
fabric/dependencies/flex-auth-api-topaz-runtime.yaml
Normal file
28
fabric/dependencies/flex-auth-api-topaz-runtime.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.needs-topaz-runtime
|
||||||
|
name: flex-auth Topaz runtime dependency
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
source_links:
|
||||||
|
- label: Seed dependency declaration
|
||||||
|
path: fabric/README.md
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
consumer_service_id: flex-auth.api
|
||||||
|
requires:
|
||||||
|
capability_type: authorization-decision-service
|
||||||
|
capability_id: flex-auth.topaz.authorization-runtime
|
||||||
|
interface:
|
||||||
|
type: http-api
|
||||||
|
version_constraint: ">=v1 <v2"
|
||||||
|
auth:
|
||||||
|
method: oidc
|
||||||
|
criticality: high
|
||||||
|
data_classification: restricted
|
||||||
|
fallback:
|
||||||
|
mode: degraded
|
||||||
|
description: flex-auth can fall back to limited local policy checks only for non-critical paths.
|
||||||
28
fabric/dependencies/the-custodian-state-hub-postgresql.yaml
Normal file
28
fabric/dependencies/the-custodian-state-hub-postgresql.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
id: the-custodian.state-hub.needs-postgresql
|
||||||
|
name: State Hub PostgreSQL dependency
|
||||||
|
owner: the-custodian
|
||||||
|
repo: the-custodian
|
||||||
|
domain: custodian
|
||||||
|
source_links:
|
||||||
|
- label: Seed dependency declaration
|
||||||
|
path: fabric/README.md
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
consumer_service_id: the-custodian.state-hub
|
||||||
|
requires:
|
||||||
|
capability_type: postgresql-database-service
|
||||||
|
capability_id: railiance-platform.cnpg.postgresql
|
||||||
|
interface:
|
||||||
|
type: database-connection
|
||||||
|
version_constraint: ">=16 <17"
|
||||||
|
auth:
|
||||||
|
method: database_role
|
||||||
|
criticality: critical
|
||||||
|
data_classification: confidential
|
||||||
|
fallback:
|
||||||
|
mode: none
|
||||||
|
description: State Hub cannot persist coordination state without PostgreSQL.
|
||||||
20
fabric/interfaces/artifact-store-object-storage-bucket.yaml
Normal file
20
fabric/interfaces/artifact-store-object-storage-bucket.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: artifact-store.object-storage.bucket
|
||||||
|
name: artifact-store object bucket
|
||||||
|
owner: artifact-store
|
||||||
|
repo: artifact-store
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: planned
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Bucket and object layout contract for artifact storage.
|
||||||
|
interface_type: object-storage-bucket
|
||||||
|
version: v1
|
||||||
|
service_id: artifact-store.storage-service
|
||||||
|
capability_ids:
|
||||||
|
- artifact-store.object-storage
|
||||||
|
auth:
|
||||||
|
method: sts_token
|
||||||
|
data_classification: confidential
|
||||||
20
fabric/interfaces/artifact-store-object-storage-sts.yaml
Normal file
20
fabric/interfaces/artifact-store-object-storage-sts.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: artifact-store.object-storage.sts
|
||||||
|
name: artifact-store STS credential endpoint
|
||||||
|
owner: artifact-store
|
||||||
|
repo: artifact-store
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: planned
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Temporary scoped credential vending interface for object storage.
|
||||||
|
interface_type: sts-token
|
||||||
|
version: v1
|
||||||
|
service_id: artifact-store.storage-service
|
||||||
|
capability_ids:
|
||||||
|
- artifact-store.object-storage.credentials
|
||||||
|
auth:
|
||||||
|
method: oidc
|
||||||
|
data_classification: secret
|
||||||
20
fabric/interfaces/flex-auth-api-http-api.yaml
Normal file
20
fabric/interfaces/flex-auth-api-http-api.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.http-api
|
||||||
|
name: flex-auth decision HTTP API
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: HTTP API for authorization decision requests.
|
||||||
|
interface_type: http-api
|
||||||
|
version: v1
|
||||||
|
service_id: flex-auth.api
|
||||||
|
capability_ids:
|
||||||
|
- flex-auth.api.authorization-decisions
|
||||||
|
auth:
|
||||||
|
method: oidc
|
||||||
|
data_classification: restricted
|
||||||
20
fabric/interfaces/flex-auth-api-policy-package.yaml
Normal file
20
fabric/interfaces/flex-auth-api-policy-package.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api.policy-package
|
||||||
|
name: flex-auth policy package
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Versioned authorization policy package consumed by PDP runtimes.
|
||||||
|
interface_type: policy-package
|
||||||
|
version: v1
|
||||||
|
service_id: flex-auth.api
|
||||||
|
capability_ids:
|
||||||
|
- flex-auth.api.authorization-decisions
|
||||||
|
auth:
|
||||||
|
method: oidc
|
||||||
|
data_classification: restricted
|
||||||
20
fabric/interfaces/flex-auth-topaz-http-api.yaml
Normal file
20
fabric/interfaces/flex-auth-topaz-http-api.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.topaz.http-api
|
||||||
|
name: Topaz decision HTTP API
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: HTTP interface for delegated Topaz authorization decisions.
|
||||||
|
interface_type: http-api
|
||||||
|
version: v1
|
||||||
|
service_id: flex-auth.topaz
|
||||||
|
capability_ids:
|
||||||
|
- flex-auth.topaz.authorization-runtime
|
||||||
|
auth:
|
||||||
|
method: oidc
|
||||||
|
data_classification: restricted
|
||||||
20
fabric/interfaces/key-cape-iam-profile-http-api.yaml
Normal file
20
fabric/interfaces/key-cape-iam-profile-http-api.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: key-cape.iam-profile.http-api
|
||||||
|
name: key-cape IAM Profile HTTP API
|
||||||
|
owner: key-cape
|
||||||
|
repo: key-cape
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: HTTP API for IAM Profile lookup and claim serving.
|
||||||
|
interface_type: http-api
|
||||||
|
version: v1
|
||||||
|
service_id: key-cape.iam-profile
|
||||||
|
capability_ids:
|
||||||
|
- key-cape.iam-profile.issuer
|
||||||
|
auth:
|
||||||
|
method: oidc
|
||||||
|
data_classification: restricted
|
||||||
20
fabric/interfaces/key-cape-iam-profile-oidc-discovery.yaml
Normal file
20
fabric/interfaces/key-cape-iam-profile-oidc-discovery.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: key-cape.iam-profile.oidc-discovery
|
||||||
|
name: key-cape OIDC discovery
|
||||||
|
owner: key-cape
|
||||||
|
repo: key-cape
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: OIDC discovery metadata for IAM Profile claims.
|
||||||
|
interface_type: oidc-discovery
|
||||||
|
version: v1
|
||||||
|
service_id: key-cape.iam-profile
|
||||||
|
capability_ids:
|
||||||
|
- key-cape.iam-profile.issuer
|
||||||
|
auth:
|
||||||
|
method: none
|
||||||
|
data_classification: public
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: net-kingdom.iam-profile.oidc-discovery
|
||||||
|
name: NetKingdom IAM Profile discovery
|
||||||
|
owner: net-kingdom
|
||||||
|
repo: net-kingdom
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: OIDC discovery contract for IAM Profile identity architecture.
|
||||||
|
interface_type: oidc-discovery
|
||||||
|
version: v1
|
||||||
|
service_id: net-kingdom.iam-profile
|
||||||
|
capability_ids:
|
||||||
|
- net-kingdom.iam-profile.issuer
|
||||||
|
auth:
|
||||||
|
method: none
|
||||||
|
data_classification: public
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.cnpg.database-connection
|
||||||
|
name: CloudNativePG database connection
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: PostgreSQL network endpoint and credential contract.
|
||||||
|
interface_type: database-connection
|
||||||
|
version: "16"
|
||||||
|
service_id: railiance-platform.cnpg
|
||||||
|
capability_ids:
|
||||||
|
- railiance-platform.cnpg.postgresql
|
||||||
|
auth:
|
||||||
|
method: database_role
|
||||||
|
data_classification: confidential
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao.database-roles
|
||||||
|
name: OpenBao database dynamic credential roles
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Dynamic credential role interface for database access.
|
||||||
|
interface_type: openbao-dynamic-credential-role
|
||||||
|
version: v1
|
||||||
|
service_id: railiance-platform.openbao
|
||||||
|
endpoint:
|
||||||
|
path: database/creds
|
||||||
|
auth:
|
||||||
|
method: kubernetes_service_account
|
||||||
|
audience: openbao
|
||||||
|
data_classification: secret
|
||||||
23
fabric/interfaces/railiance-platform-openbao-kv-v2.yaml
Normal file
23
fabric/interfaces/railiance-platform-openbao-kv-v2.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.openbao.kv-v2
|
||||||
|
name: OpenBao KV v2 mount
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: KV v2 secret mount for approved Railiance workload secrets.
|
||||||
|
interface_type: openbao-kv-v2-mount
|
||||||
|
version: v1
|
||||||
|
service_id: railiance-platform.openbao
|
||||||
|
capability_ids:
|
||||||
|
- railiance-platform.openbao.runtime-secrets
|
||||||
|
endpoint:
|
||||||
|
path: secret/data/railiance
|
||||||
|
auth:
|
||||||
|
method: kubernetes_service_account
|
||||||
|
audience: openbao
|
||||||
|
data_classification: secret
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.valkey.database-connection
|
||||||
|
name: Valkey Redis-compatible connection
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Redis protocol compatible cache endpoint and credential contract.
|
||||||
|
interface_type: database-connection
|
||||||
|
version: v1
|
||||||
|
service_id: railiance-platform.valkey
|
||||||
|
capability_ids:
|
||||||
|
- railiance-platform.valkey.cache
|
||||||
|
auth:
|
||||||
|
method: static_secret
|
||||||
|
data_classification: internal
|
||||||
20
fabric/interfaces/repo-scoping-scope-generator-cli.yaml
Normal file
20
fabric/interfaces/repo-scoping-scope-generator-cli.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: repo-scoping.scope-generator.cli
|
||||||
|
name: repo-scoping CLI
|
||||||
|
owner: repo-scoping
|
||||||
|
repo: repo-scoping
|
||||||
|
domain: custodian
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: CLI interface for producing scope descriptions.
|
||||||
|
interface_type: cli
|
||||||
|
version: v1
|
||||||
|
service_id: repo-scoping.scope-generator
|
||||||
|
capability_ids:
|
||||||
|
- repo-scoping.scope-generation
|
||||||
|
auth:
|
||||||
|
method: none
|
||||||
|
data_classification: internal
|
||||||
20
fabric/interfaces/the-custodian-state-hub-http-api.yaml
Normal file
20
fabric/interfaces/the-custodian-state-hub-http-api.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: the-custodian.state-hub.http-api
|
||||||
|
name: State Hub HTTP API
|
||||||
|
owner: the-custodian
|
||||||
|
repo: the-custodian
|
||||||
|
domain: custodian
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: HTTP API for coordination state and progress tracking.
|
||||||
|
interface_type: http-api
|
||||||
|
version: v1
|
||||||
|
service_id: the-custodian.state-hub
|
||||||
|
capability_ids:
|
||||||
|
- the-custodian.state-hub.coordination
|
||||||
|
auth:
|
||||||
|
method: none
|
||||||
|
data_classification: internal
|
||||||
19
fabric/services/artifact-store-object-storage.yaml
Normal file
19
fabric/services/artifact-store-object-storage.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: artifact-store.storage-service
|
||||||
|
name: artifact-store object storage
|
||||||
|
owner: artifact-store
|
||||||
|
repo: artifact-store
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: planned
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Planned object storage and scoped credential vending service.
|
||||||
|
service_type: storage-service
|
||||||
|
provides_capabilities:
|
||||||
|
- artifact-store.object-storage
|
||||||
|
- artifact-store.object-storage.credentials
|
||||||
|
exposes_interfaces:
|
||||||
|
- artifact-store.object-storage.bucket
|
||||||
|
- artifact-store.object-storage.sts
|
||||||
18
fabric/services/flex-auth-api.yaml
Normal file
18
fabric/services/flex-auth-api.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.api
|
||||||
|
name: flex-auth API
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Authorization policy and decision control plane.
|
||||||
|
service_type: authorization-service
|
||||||
|
provides_capabilities:
|
||||||
|
- flex-auth.api.authorization-decisions
|
||||||
|
exposes_interfaces:
|
||||||
|
- flex-auth.api.http-api
|
||||||
|
- flex-auth.api.policy-package
|
||||||
17
fabric/services/flex-auth-topaz.yaml
Normal file
17
fabric/services/flex-auth-topaz.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: flex-auth.topaz
|
||||||
|
name: Topaz delegated PDP
|
||||||
|
owner: flex-auth
|
||||||
|
repo: flex-auth
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Delegated policy decision runtime used by flex-auth.
|
||||||
|
service_type: authorization-runtime
|
||||||
|
provides_capabilities:
|
||||||
|
- flex-auth.topaz.authorization-runtime
|
||||||
|
exposes_interfaces:
|
||||||
|
- flex-auth.topaz.http-api
|
||||||
18
fabric/services/key-cape-iam-profile.yaml
Normal file
18
fabric/services/key-cape-iam-profile.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: key-cape.iam-profile
|
||||||
|
name: key-cape IAM Profile API
|
||||||
|
owner: key-cape
|
||||||
|
repo: key-cape
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Lightweight IAM Profile implementation for Railiance workloads.
|
||||||
|
service_type: identity-service
|
||||||
|
provides_capabilities:
|
||||||
|
- key-cape.iam-profile.issuer
|
||||||
|
exposes_interfaces:
|
||||||
|
- key-cape.iam-profile.http-api
|
||||||
|
- key-cape.iam-profile.oidc-discovery
|
||||||
17
fabric/services/net-kingdom-iam-profile.yaml
Normal file
17
fabric/services/net-kingdom-iam-profile.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: net-kingdom.iam-profile
|
||||||
|
name: NetKingdom IAM Profile
|
||||||
|
owner: net-kingdom
|
||||||
|
repo: net-kingdom
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: Identity and security architecture contract for IAM Profile claims.
|
||||||
|
service_type: identity-contract
|
||||||
|
provides_capabilities:
|
||||||
|
- net-kingdom.iam-profile.issuer
|
||||||
|
exposes_interfaces:
|
||||||
|
- net-kingdom.iam-profile.oidc-discovery
|
||||||
17
fabric/services/railiance-platform-cnpg.yaml
Normal file
17
fabric/services/railiance-platform-cnpg.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.cnpg
|
||||||
|
name: CloudNativePG
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: PostgreSQL database service for Railiance platform and app workloads.
|
||||||
|
service_type: database-service
|
||||||
|
provides_capabilities:
|
||||||
|
- railiance-platform.cnpg.postgresql
|
||||||
|
exposes_interfaces:
|
||||||
|
- railiance-platform.cnpg.database-connection
|
||||||
18
fabric/services/railiance-platform-openbao.yaml
Normal file
18
fabric/services/railiance-platform-openbao.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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]
|
||||||
|
description: OpenBao service used by Railiance workloads for runtime secrets.
|
||||||
|
service_type: platform-service
|
||||||
|
provides_capabilities:
|
||||||
|
- railiance-platform.openbao.runtime-secrets
|
||||||
|
exposes_interfaces:
|
||||||
|
- railiance-platform.openbao.kv-v2
|
||||||
|
- railiance-platform.openbao.database-roles
|
||||||
17
fabric/services/railiance-platform-valkey.yaml
Normal file
17
fabric/services/railiance-platform-valkey.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: railiance-platform.valkey
|
||||||
|
name: Valkey
|
||||||
|
owner: railiance-platform
|
||||||
|
repo: railiance-platform
|
||||||
|
domain: railiance
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [dev, staging, prod]
|
||||||
|
description: Redis-compatible cache for Railiance workloads.
|
||||||
|
service_type: cache-service
|
||||||
|
provides_capabilities:
|
||||||
|
- railiance-platform.valkey.cache
|
||||||
|
exposes_interfaces:
|
||||||
|
- railiance-platform.valkey.database-connection
|
||||||
17
fabric/services/repo-scoping-scope-generator.yaml
Normal file
17
fabric/services/repo-scoping-scope-generator.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: repo-scoping.scope-generator
|
||||||
|
name: repo-scoping scope generator
|
||||||
|
owner: repo-scoping
|
||||||
|
repo: repo-scoping
|
||||||
|
domain: custodian
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: Generates repo scope and usefulness descriptions for humans and agents.
|
||||||
|
service_type: planning-tool
|
||||||
|
provides_capabilities:
|
||||||
|
- repo-scoping.scope-generation
|
||||||
|
exposes_interfaces:
|
||||||
|
- repo-scoping.scope-generator.cli
|
||||||
17
fabric/services/the-custodian-state-hub.yaml
Normal file
17
fabric/services/the-custodian-state-hub.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: railiance.fabric/v1alpha1
|
||||||
|
kind: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
id: the-custodian.state-hub
|
||||||
|
name: State Hub
|
||||||
|
owner: the-custodian
|
||||||
|
repo: the-custodian
|
||||||
|
domain: custodian
|
||||||
|
spec:
|
||||||
|
lifecycle: active
|
||||||
|
environments: [all]
|
||||||
|
description: Coordination read model for repos, workstreams, tasks, decisions, and progress.
|
||||||
|
service_type: coordination-service
|
||||||
|
provides_capabilities:
|
||||||
|
- the-custodian.state-hub.coordination
|
||||||
|
exposes_interfaces:
|
||||||
|
- the-custodian.state-hub.http-api
|
||||||
20
pyproject.toml
Normal file
20
pyproject.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "railiance-fabric"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Railiance ecosystem graph declaration loader and validator"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"jsonschema>=4.18",
|
||||||
|
"PyYAML>=6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
railiance-fabric = "railiance_fabric.cli:main"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["railiance_fabric*"]
|
||||||
5
railiance_fabric/__init__.py
Normal file
5
railiance_fabric/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""Railiance Fabric graph tooling."""
|
||||||
|
|
||||||
|
__all__ = ["__version__"]
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
183
railiance_fabric/cli.py
Normal file
183
railiance_fabric/cli.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .graph import FabricGraph, build_graph
|
||||||
|
from .validation import validate_roots
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="railiance-fabric",
|
||||||
|
description="Load and validate Railiance Fabric declarations.",
|
||||||
|
)
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
validate = sub.add_parser(
|
||||||
|
"validate",
|
||||||
|
help="Validate one or more repo roots or declaration files.",
|
||||||
|
)
|
||||||
|
validate.add_argument(
|
||||||
|
"paths",
|
||||||
|
nargs="+",
|
||||||
|
type=Path,
|
||||||
|
help="Repo root, fabric directory, or declaration YAML file.",
|
||||||
|
)
|
||||||
|
validate.add_argument(
|
||||||
|
"--warnings-as-errors",
|
||||||
|
action="store_true",
|
||||||
|
help="Exit non-zero when warnings are present.",
|
||||||
|
)
|
||||||
|
|
||||||
|
providers = sub.add_parser("providers", help="List providers for a capability type or id.")
|
||||||
|
providers.add_argument("capability", help="Capability type or capability id.")
|
||||||
|
providers.add_argument("paths", nargs="*", type=Path, default=[Path(".")])
|
||||||
|
|
||||||
|
consumers = sub.add_parser("consumers", help="List consumers of a capability or interface.")
|
||||||
|
consumers.add_argument("target", help="Capability/interface type or declaration id.")
|
||||||
|
consumers.add_argument("paths", nargs="*", type=Path, default=[Path(".")])
|
||||||
|
|
||||||
|
dependency_path = sub.add_parser("dependency-path", help="Show dependency path for a service.")
|
||||||
|
dependency_path.add_argument("service_id", help="Service declaration id.")
|
||||||
|
dependency_path.add_argument("paths", nargs="*", type=Path, default=[Path(".")])
|
||||||
|
|
||||||
|
unresolved = sub.add_parser("unresolved", help="Show missing or unresolved dependencies.")
|
||||||
|
unresolved.add_argument("paths", nargs="*", type=Path, default=[Path(".")])
|
||||||
|
|
||||||
|
blast = sub.add_parser("blast-radius", help="Show consumers affected by an interface change.")
|
||||||
|
blast.add_argument("interface", help="Interface type or interface declaration id.")
|
||||||
|
blast.add_argument("paths", nargs="*", type=Path, default=[Path(".")])
|
||||||
|
|
||||||
|
export = sub.add_parser("export", help="Export graph as JSON or Mermaid.")
|
||||||
|
export.add_argument("paths", nargs="*", type=Path, default=[Path(".")])
|
||||||
|
export.add_argument("--format", choices=["json", "mermaid"], default="json")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
if args.command == "validate":
|
||||||
|
report = validate_roots(args.paths)
|
||||||
|
for diagnostic in report.diagnostics:
|
||||||
|
print(diagnostic.format())
|
||||||
|
print(report.summary())
|
||||||
|
if report.errors:
|
||||||
|
return 1
|
||||||
|
if args.warnings_as_errors and report.warnings:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "providers":
|
||||||
|
graph = _load_graph_or_exit(args.paths)
|
||||||
|
_print_providers(graph, args.capability)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "consumers":
|
||||||
|
graph = _load_graph_or_exit(args.paths)
|
||||||
|
_print_consumers(graph, args.target)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "dependency-path":
|
||||||
|
graph = _load_graph_or_exit(args.paths)
|
||||||
|
print("\n".join(graph.dependency_path_lines(args.service_id)))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "unresolved":
|
||||||
|
graph = _load_graph_or_exit(args.paths)
|
||||||
|
_print_unresolved(graph)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "blast-radius":
|
||||||
|
graph = _load_graph_or_exit(args.paths)
|
||||||
|
_print_consumers(graph, args.interface, matches=graph.blast_radius(args.interface))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "export":
|
||||||
|
graph = _load_graph_or_exit(args.paths)
|
||||||
|
print(graph.to_mermaid() if args.format == "mermaid" else graph.to_json())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
parser.error(f"unknown command {args.command!r}")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
def _load_graph_or_exit(paths: list[Path]) -> FabricGraph:
|
||||||
|
graph = build_graph(paths)
|
||||||
|
if graph.load_errors:
|
||||||
|
for path, message in graph.load_errors:
|
||||||
|
print(f"ERROR {path}: {message}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
return graph
|
||||||
|
|
||||||
|
|
||||||
|
def _print_providers(graph: FabricGraph, capability: str) -> None:
|
||||||
|
providers = graph.providers(capability)
|
||||||
|
if not providers:
|
||||||
|
print(f"no providers found for {capability}")
|
||||||
|
return
|
||||||
|
print("provider_id\tservice_id\tlifecycle\tenvironments\tinterfaces")
|
||||||
|
for provider in providers:
|
||||||
|
spec = provider.spec
|
||||||
|
print(
|
||||||
|
"\t".join(
|
||||||
|
[
|
||||||
|
provider.id,
|
||||||
|
str(spec.get("service_id", "")),
|
||||||
|
str(spec.get("lifecycle", "")),
|
||||||
|
",".join(spec.get("environments", [])),
|
||||||
|
",".join(spec.get("interface_ids", [])),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_consumers(
|
||||||
|
graph: FabricGraph,
|
||||||
|
target: str,
|
||||||
|
matches: object | None = None,
|
||||||
|
) -> None:
|
||||||
|
consumer_matches = graph.consumers(target) if matches is None else list(matches)
|
||||||
|
if not consumer_matches:
|
||||||
|
print(f"no consumers found for {target}")
|
||||||
|
return
|
||||||
|
print("consumer_service_id\tdependency_id\trequires\tprovider_capability_id\tprovider_interface_id\tstatus")
|
||||||
|
for match in consumer_matches:
|
||||||
|
print(
|
||||||
|
"\t".join(
|
||||||
|
[
|
||||||
|
match.consumer_service_id,
|
||||||
|
match.dependency_id,
|
||||||
|
match.required_capability_type,
|
||||||
|
match.provider_capability_id,
|
||||||
|
match.provider_interface_id,
|
||||||
|
match.status,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_unresolved(graph: FabricGraph) -> None:
|
||||||
|
unresolved = graph.unresolved_dependencies()
|
||||||
|
if not unresolved:
|
||||||
|
print("no unresolved dependencies")
|
||||||
|
return
|
||||||
|
print("dependency_id\tconsumer_service_id\trequires")
|
||||||
|
for dependency in unresolved:
|
||||||
|
spec = dependency.spec
|
||||||
|
requires = spec.get("requires", {})
|
||||||
|
print(
|
||||||
|
"\t".join(
|
||||||
|
[
|
||||||
|
dependency.id,
|
||||||
|
str(spec.get("consumer_service_id", "")),
|
||||||
|
str(requires.get("capability_id") or requires.get("capability_type", "")),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
264
railiance_fabric/graph.py
Normal file
264
railiance_fabric/graph.py
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .loader import load_declarations
|
||||||
|
from .model import Declaration
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ConsumerMatch:
|
||||||
|
consumer_service_id: str
|
||||||
|
dependency_id: str
|
||||||
|
required_capability_type: str
|
||||||
|
provider_capability_id: str = ""
|
||||||
|
provider_interface_id: str = ""
|
||||||
|
status: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FabricGraph:
|
||||||
|
declarations: list[Declaration]
|
||||||
|
load_errors: list[tuple[Path, str]] = field(default_factory=list)
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
self.by_id: dict[str, Declaration] = {}
|
||||||
|
self.by_kind: dict[str, list[Declaration]] = defaultdict(list)
|
||||||
|
for declaration in self.declarations:
|
||||||
|
if declaration.id and declaration.id not in self.by_id:
|
||||||
|
self.by_id[declaration.id] = declaration
|
||||||
|
self.by_kind[declaration.kind].append(declaration)
|
||||||
|
|
||||||
|
self.services = {d.id: d for d in self.by_kind["ServiceDeclaration"]}
|
||||||
|
self.capabilities = {d.id: d for d in self.by_kind["CapabilityDeclaration"]}
|
||||||
|
self.interfaces = {d.id: d for d in self.by_kind["InterfaceDeclaration"]}
|
||||||
|
self.dependencies = {d.id: d for d in self.by_kind["DependencyDeclaration"]}
|
||||||
|
self.bindings = {d.id: d for d in self.by_kind["BindingAssertion"]}
|
||||||
|
self.bindings_by_dependency: dict[str, list[Declaration]] = defaultdict(list)
|
||||||
|
for binding in self.bindings.values():
|
||||||
|
dependency_id = str(binding.spec.get("dependency_id", ""))
|
||||||
|
if dependency_id:
|
||||||
|
self.bindings_by_dependency[dependency_id].append(binding)
|
||||||
|
|
||||||
|
def providers(self, capability: str) -> list[Declaration]:
|
||||||
|
if capability in self.capabilities:
|
||||||
|
return [self.capabilities[capability]]
|
||||||
|
return [
|
||||||
|
declaration
|
||||||
|
for declaration in self.capabilities.values()
|
||||||
|
if declaration.spec.get("capability_type") == capability
|
||||||
|
]
|
||||||
|
|
||||||
|
def consumers(self, target: str) -> list[ConsumerMatch]:
|
||||||
|
matches: list[ConsumerMatch] = []
|
||||||
|
target_interface_type = self.interfaces.get(target, Declaration(Path(), {})).spec.get("interface_type")
|
||||||
|
|
||||||
|
for dependency in self.dependencies.values():
|
||||||
|
spec = dependency.spec
|
||||||
|
requires = spec.get("requires", {})
|
||||||
|
dependency_matches = (
|
||||||
|
target == dependency.id
|
||||||
|
or target == requires.get("capability_id")
|
||||||
|
or target == requires.get("capability_type")
|
||||||
|
or target == spec.get("interface", {}).get("type")
|
||||||
|
or (target_interface_type and target_interface_type == spec.get("interface", {}).get("type"))
|
||||||
|
)
|
||||||
|
|
||||||
|
dependency_bindings = self.bindings_by_dependency.get(dependency.id, [])
|
||||||
|
binding_matches = [
|
||||||
|
binding
|
||||||
|
for binding in dependency_bindings
|
||||||
|
if target in {
|
||||||
|
binding.spec.get("provider_capability_id"),
|
||||||
|
binding.spec.get("provider_interface_id"),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if dependency_matches and not dependency_bindings:
|
||||||
|
matches.append(
|
||||||
|
ConsumerMatch(
|
||||||
|
consumer_service_id=str(spec.get("consumer_service_id", "")),
|
||||||
|
dependency_id=dependency.id,
|
||||||
|
required_capability_type=str(requires.get("capability_type", "")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for binding in dependency_bindings:
|
||||||
|
if dependency_matches or binding in binding_matches:
|
||||||
|
matches.append(
|
||||||
|
ConsumerMatch(
|
||||||
|
consumer_service_id=str(spec.get("consumer_service_id", "")),
|
||||||
|
dependency_id=dependency.id,
|
||||||
|
required_capability_type=str(requires.get("capability_type", "")),
|
||||||
|
provider_capability_id=str(binding.spec.get("provider_capability_id", "")),
|
||||||
|
provider_interface_id=str(binding.spec.get("provider_interface_id", "")),
|
||||||
|
status=str(binding.spec.get("status", "")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return sorted(matches, key=lambda item: (item.consumer_service_id, item.dependency_id))
|
||||||
|
|
||||||
|
def unresolved_dependencies(self) -> list[Declaration]:
|
||||||
|
unresolved: list[Declaration] = []
|
||||||
|
for dependency in self.dependencies.values():
|
||||||
|
providers = self.matching_providers(dependency)
|
||||||
|
bindings = self.bindings_by_dependency.get(dependency.id, [])
|
||||||
|
has_missing_binding = any(binding.spec.get("status") in {"missing", "disputed"} for binding in bindings)
|
||||||
|
if not providers or has_missing_binding:
|
||||||
|
unresolved.append(dependency)
|
||||||
|
return sorted(unresolved, key=lambda item: item.id)
|
||||||
|
|
||||||
|
def matching_providers(self, dependency: Declaration) -> list[Declaration]:
|
||||||
|
requires = dependency.spec.get("requires", {})
|
||||||
|
capability_id = requires.get("capability_id")
|
||||||
|
if capability_id:
|
||||||
|
provider = self.capabilities.get(str(capability_id))
|
||||||
|
return [provider] if provider is not None else []
|
||||||
|
capability_type = requires.get("capability_type")
|
||||||
|
return [
|
||||||
|
provider
|
||||||
|
for provider in self.capabilities.values()
|
||||||
|
if provider.spec.get("capability_type") == capability_type
|
||||||
|
]
|
||||||
|
|
||||||
|
def dependency_path_lines(self, service_id: str) -> list[str]:
|
||||||
|
if service_id not in self.services:
|
||||||
|
return [f"unknown service: {service_id}"]
|
||||||
|
|
||||||
|
lines: list[str] = []
|
||||||
|
def walk(current: str, indent: int, stack: list[str]) -> None:
|
||||||
|
prefix = " " * indent
|
||||||
|
if current in stack:
|
||||||
|
lines.append(f"{prefix}{current} (cycle)")
|
||||||
|
return
|
||||||
|
lines.append(f"{prefix}{current}")
|
||||||
|
deps = [
|
||||||
|
dep
|
||||||
|
for dep in self.dependencies.values()
|
||||||
|
if dep.spec.get("consumer_service_id") == current
|
||||||
|
]
|
||||||
|
if not deps:
|
||||||
|
lines.append(f"{prefix} no declared dependencies")
|
||||||
|
return
|
||||||
|
for dep in sorted(deps, key=lambda item: item.id):
|
||||||
|
required = dep.spec.get("requires", {}).get("capability_type", "")
|
||||||
|
lines.append(f"{prefix} requires {required}: {dep.id}")
|
||||||
|
bindings = self.bindings_by_dependency.get(dep.id, [])
|
||||||
|
if not bindings:
|
||||||
|
providers = self.matching_providers(dep)
|
||||||
|
if providers:
|
||||||
|
for provider in providers:
|
||||||
|
lines.append(f"{prefix} candidate {provider.id}")
|
||||||
|
else:
|
||||||
|
lines.append(f"{prefix} unresolved")
|
||||||
|
continue
|
||||||
|
for binding in sorted(bindings, key=lambda item: item.id):
|
||||||
|
provider_id = str(binding.spec.get("provider_capability_id", ""))
|
||||||
|
provider = self.capabilities.get(provider_id)
|
||||||
|
provider_service = provider.spec.get("service_id") if provider else ""
|
||||||
|
status = binding.spec.get("status", "")
|
||||||
|
lines.append(f"{prefix} {status} -> {provider_id}")
|
||||||
|
if provider_service and provider_service != current:
|
||||||
|
walk(str(provider_service), indent + 3, stack + [current])
|
||||||
|
|
||||||
|
walk(service_id, 0, [])
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def blast_radius(self, interface: str) -> list[ConsumerMatch]:
|
||||||
|
if interface in self.interfaces:
|
||||||
|
return [
|
||||||
|
match
|
||||||
|
for match in self.consumers(interface)
|
||||||
|
if match.provider_interface_id == interface
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
match
|
||||||
|
for match in self.consumers(interface)
|
||||||
|
if self.dependencies[match.dependency_id].spec.get("interface", {}).get("type") == interface
|
||||||
|
]
|
||||||
|
|
||||||
|
def to_export(self) -> dict[str, Any]:
|
||||||
|
nodes: list[dict[str, Any]] = []
|
||||||
|
edges: list[dict[str, str]] = []
|
||||||
|
|
||||||
|
for declaration in sorted(self.declarations, key=lambda item: (item.kind, item.id)):
|
||||||
|
nodes.append(
|
||||||
|
{
|
||||||
|
"id": declaration.id,
|
||||||
|
"kind": declaration.kind,
|
||||||
|
"name": declaration.metadata.get("name", declaration.id),
|
||||||
|
"repo": declaration.metadata.get("repo", ""),
|
||||||
|
"domain": declaration.metadata.get("domain", ""),
|
||||||
|
"lifecycle": declaration.spec.get("lifecycle", ""),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for service in self.services.values():
|
||||||
|
for capability_id in service.spec.get("provides_capabilities", []):
|
||||||
|
edges.append({"from": service.id, "to": capability_id, "type": "provides"})
|
||||||
|
for interface_id in service.spec.get("exposes_interfaces", []):
|
||||||
|
edges.append({"from": service.id, "to": interface_id, "type": "exposes"})
|
||||||
|
|
||||||
|
for capability in self.capabilities.values():
|
||||||
|
for interface_id in capability.spec.get("interface_ids", []):
|
||||||
|
edges.append({"from": capability.id, "to": interface_id, "type": "available_via"})
|
||||||
|
|
||||||
|
for dependency in self.dependencies.values():
|
||||||
|
consumer = str(dependency.spec.get("consumer_service_id", ""))
|
||||||
|
if consumer:
|
||||||
|
edges.append({"from": consumer, "to": dependency.id, "type": "consumes"})
|
||||||
|
for binding in self.bindings_by_dependency.get(dependency.id, []):
|
||||||
|
edges.append(
|
||||||
|
{
|
||||||
|
"from": dependency.id,
|
||||||
|
"to": str(binding.spec.get("provider_capability_id", "")),
|
||||||
|
"type": f"binds:{binding.spec.get('status', '')}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
interface_id = str(binding.spec.get("provider_interface_id", ""))
|
||||||
|
if interface_id:
|
||||||
|
edges.append({"from": dependency.id, "to": interface_id, "type": "uses_interface"})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"apiVersion": "railiance.fabric/v1alpha1",
|
||||||
|
"kind": "FabricGraphExport",
|
||||||
|
"nodes": nodes,
|
||||||
|
"edges": edges,
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
return json.dumps(self.to_export(), indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
def to_mermaid(self) -> str:
|
||||||
|
export = self.to_export()
|
||||||
|
lines = ["graph TD"]
|
||||||
|
for node in export["nodes"]:
|
||||||
|
node_id = _mermaid_id(node["id"])
|
||||||
|
label = f"{node['name']}\\n{node['kind']}"
|
||||||
|
lines.append(f' {node_id}["{_escape_mermaid(label)}"]')
|
||||||
|
for edge in export["edges"]:
|
||||||
|
source = _mermaid_id(edge["from"])
|
||||||
|
target = _mermaid_id(edge["to"])
|
||||||
|
label = _escape_mermaid(edge["type"])
|
||||||
|
lines.append(f" {source} -- {label} --> {target}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def build_graph(paths: list[Path]) -> FabricGraph:
|
||||||
|
declarations, load_errors = load_declarations(paths)
|
||||||
|
return FabricGraph(declarations=declarations, load_errors=load_errors)
|
||||||
|
|
||||||
|
|
||||||
|
def _mermaid_id(value: str) -> str:
|
||||||
|
cleaned = re.sub(r"[^A-Za-z0-9_]", "_", value)
|
||||||
|
if cleaned and cleaned[0].isdigit():
|
||||||
|
cleaned = "_" + cleaned
|
||||||
|
return cleaned or "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_mermaid(value: str) -> str:
|
||||||
|
return value.replace('"', '\\"')
|
||||||
56
railiance_fabric/loader.py
Normal file
56
railiance_fabric/loader.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from .model import Declaration
|
||||||
|
|
||||||
|
DECLARATION_DIRS = ("services", "capabilities", "interfaces", "dependencies", "bindings")
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root() -> Path:
|
||||||
|
return Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def declaration_files(path: Path) -> list[Path]:
|
||||||
|
path = path.resolve()
|
||||||
|
if path.is_file():
|
||||||
|
return [path]
|
||||||
|
|
||||||
|
fabric = path if path.name == "fabric" else path / "fabric"
|
||||||
|
files: list[Path] = []
|
||||||
|
for directory in DECLARATION_DIRS:
|
||||||
|
root = fabric / directory
|
||||||
|
if root.is_dir():
|
||||||
|
files.extend(sorted(root.glob("*.yaml")))
|
||||||
|
files.extend(sorted(root.glob("*.yml")))
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def load_yaml(path: Path) -> Any:
|
||||||
|
return yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def load_declarations(paths: list[Path]) -> tuple[list[Declaration], list[tuple[Path, str]]]:
|
||||||
|
declarations: list[Declaration] = []
|
||||||
|
errors: list[tuple[Path, str]] = []
|
||||||
|
seen_files: set[Path] = set()
|
||||||
|
|
||||||
|
for raw in paths:
|
||||||
|
for path in declaration_files(raw):
|
||||||
|
if path in seen_files:
|
||||||
|
continue
|
||||||
|
seen_files.add(path)
|
||||||
|
try:
|
||||||
|
data = load_yaml(path)
|
||||||
|
except Exception as exc:
|
||||||
|
errors.append((path, str(exc)))
|
||||||
|
continue
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
errors.append((path, "declaration must be a YAML mapping"))
|
||||||
|
continue
|
||||||
|
declarations.append(Declaration(path=path, data=data))
|
||||||
|
|
||||||
|
return declarations, errors
|
||||||
65
railiance_fabric/model.py
Normal file
65
railiance_fabric/model.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Declaration:
|
||||||
|
path: Path
|
||||||
|
data: dict[str, Any]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kind(self) -> str:
|
||||||
|
return str(self.data.get("kind", ""))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> str:
|
||||||
|
return str(self.data.get("metadata", {}).get("id", ""))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def spec(self) -> dict[str, Any]:
|
||||||
|
spec = self.data.get("spec", {})
|
||||||
|
return spec if isinstance(spec, dict) else {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def metadata(self) -> dict[str, Any]:
|
||||||
|
meta = self.data.get("metadata", {})
|
||||||
|
return meta if isinstance(meta, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Diagnostic:
|
||||||
|
severity: str
|
||||||
|
code: str
|
||||||
|
message: str
|
||||||
|
path: Path | None = None
|
||||||
|
|
||||||
|
def format(self) -> str:
|
||||||
|
prefix = f"{self.severity} {self.code}"
|
||||||
|
if self.path is not None:
|
||||||
|
return f"{prefix} {self.path}: {self.message}"
|
||||||
|
return f"{prefix}: {self.message}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ValidationReport:
|
||||||
|
diagnostics: list[Diagnostic] = field(default_factory=list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def errors(self) -> list[Diagnostic]:
|
||||||
|
return [d for d in self.diagnostics if d.severity == "ERROR"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def warnings(self) -> list[Diagnostic]:
|
||||||
|
return [d for d in self.diagnostics if d.severity == "WARN"]
|
||||||
|
|
||||||
|
def add(self, severity: str, code: str, message: str, path: Path | None = None) -> None:
|
||||||
|
self.diagnostics.append(Diagnostic(severity, code, message, path))
|
||||||
|
|
||||||
|
def summary(self) -> str:
|
||||||
|
return (
|
||||||
|
f"Validation complete: {len(self.errors)} error(s), "
|
||||||
|
f"{len(self.warnings)} warning(s)"
|
||||||
|
)
|
||||||
333
railiance_fabric/validation.py
Normal file
333
railiance_fabric/validation.py
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
|
||||||
|
from .loader import load_declarations, load_yaml, repo_root
|
||||||
|
from .model import Declaration, ValidationReport
|
||||||
|
|
||||||
|
SCHEMA_BY_KIND = {
|
||||||
|
"ServiceDeclaration": "service.schema.yaml",
|
||||||
|
"CapabilityDeclaration": "capability.schema.yaml",
|
||||||
|
"InterfaceDeclaration": "interface.schema.yaml",
|
||||||
|
"DependencyDeclaration": "dependency.schema.yaml",
|
||||||
|
"BindingAssertion": "binding.schema.yaml",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_roots(paths: list[Path]) -> ValidationReport:
|
||||||
|
root = repo_root()
|
||||||
|
report = ValidationReport()
|
||||||
|
declarations, load_errors = load_declarations(paths)
|
||||||
|
for path, message in load_errors:
|
||||||
|
report.add("ERROR", "load.yaml", message, path)
|
||||||
|
|
||||||
|
_validate_schema(root, declarations, report)
|
||||||
|
_validate_graph(root, declarations, report)
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_schema(root: Path, declarations: list[Declaration], report: ValidationReport) -> None:
|
||||||
|
schemas_dir = root / "schemas"
|
||||||
|
store = {
|
||||||
|
path.resolve().as_uri(): load_yaml(path)
|
||||||
|
for path in sorted(schemas_dir.glob("*.schema.yaml"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for declaration in declarations:
|
||||||
|
schema_name = SCHEMA_BY_KIND.get(declaration.kind)
|
||||||
|
if schema_name is None:
|
||||||
|
report.add(
|
||||||
|
"ERROR",
|
||||||
|
"schema.kind",
|
||||||
|
f"unknown declaration kind {declaration.kind!r}",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
schema_path = schemas_dir / schema_name
|
||||||
|
schema = load_yaml(schema_path)
|
||||||
|
resolver = jsonschema.RefResolver(
|
||||||
|
base_uri=schema_path.resolve().as_uri(),
|
||||||
|
referrer=schema,
|
||||||
|
store=store,
|
||||||
|
)
|
||||||
|
validator = jsonschema.Draft202012Validator(schema, resolver=resolver)
|
||||||
|
for error in sorted(validator.iter_errors(declaration.data), key=lambda e: list(e.path)):
|
||||||
|
location = ".".join(str(part) for part in error.path) or "<root>"
|
||||||
|
report.add(
|
||||||
|
"ERROR",
|
||||||
|
"schema.invalid",
|
||||||
|
f"{location}: {error.message}",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_graph(root: Path, declarations: list[Declaration], report: ValidationReport) -> None:
|
||||||
|
by_id: dict[str, Declaration] = {}
|
||||||
|
by_kind: dict[str, list[Declaration]] = defaultdict(list)
|
||||||
|
|
||||||
|
for declaration in declarations:
|
||||||
|
if not declaration.id:
|
||||||
|
continue
|
||||||
|
if declaration.id in by_id:
|
||||||
|
report.add(
|
||||||
|
"ERROR",
|
||||||
|
"graph.duplicate_id",
|
||||||
|
f"duplicate declaration id {declaration.id!r}",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
by_id[declaration.id] = declaration
|
||||||
|
by_kind[declaration.kind].append(declaration)
|
||||||
|
|
||||||
|
cap_types, iface_types, expected_ifaces = _load_type_catalog(root, report)
|
||||||
|
services = {d.id: d for d in by_kind["ServiceDeclaration"]}
|
||||||
|
capabilities = {d.id: d for d in by_kind["CapabilityDeclaration"]}
|
||||||
|
interfaces = {d.id: d for d in by_kind["InterfaceDeclaration"]}
|
||||||
|
dependencies = {d.id: d for d in by_kind["DependencyDeclaration"]}
|
||||||
|
|
||||||
|
for declaration in by_kind["CapabilityDeclaration"]:
|
||||||
|
spec = declaration.spec
|
||||||
|
capability_type = spec.get("capability_type")
|
||||||
|
if capability_type not in cap_types:
|
||||||
|
report.add("ERROR", "catalog.unknown_capability_type", f"unknown capability type {capability_type!r}", declaration.path)
|
||||||
|
_require_ref(report, declaration, "service_id", services, spec.get("service_id"))
|
||||||
|
for interface_id in spec.get("interface_ids", []):
|
||||||
|
_require_ref(report, declaration, "interface_ids", interfaces, interface_id)
|
||||||
|
|
||||||
|
for declaration in by_kind["InterfaceDeclaration"]:
|
||||||
|
spec = declaration.spec
|
||||||
|
interface_type = spec.get("interface_type")
|
||||||
|
if interface_type not in iface_types:
|
||||||
|
report.add("ERROR", "catalog.unknown_interface_type", f"unknown interface type {interface_type!r}", declaration.path)
|
||||||
|
_require_ref(report, declaration, "service_id", services, spec.get("service_id"))
|
||||||
|
for capability_id in spec.get("capability_ids", []):
|
||||||
|
_require_ref(report, declaration, "capability_ids", capabilities, capability_id)
|
||||||
|
|
||||||
|
for declaration in by_kind["ServiceDeclaration"]:
|
||||||
|
spec = declaration.spec
|
||||||
|
for capability_id in spec.get("provides_capabilities", []):
|
||||||
|
_require_ref(report, declaration, "provides_capabilities", capabilities, capability_id)
|
||||||
|
for interface_id in spec.get("exposes_interfaces", []):
|
||||||
|
_require_ref(report, declaration, "exposes_interfaces", interfaces, interface_id)
|
||||||
|
|
||||||
|
for declaration in by_kind["DependencyDeclaration"]:
|
||||||
|
_validate_dependency(
|
||||||
|
declaration,
|
||||||
|
report,
|
||||||
|
services,
|
||||||
|
capabilities,
|
||||||
|
interfaces,
|
||||||
|
cap_types,
|
||||||
|
iface_types,
|
||||||
|
expected_ifaces,
|
||||||
|
)
|
||||||
|
|
||||||
|
for declaration in by_kind["BindingAssertion"]:
|
||||||
|
_validate_binding(declaration, report, dependencies, capabilities, interfaces)
|
||||||
|
|
||||||
|
_detect_cycles(by_kind["DependencyDeclaration"], by_kind["BindingAssertion"], services, capabilities, report)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_type_catalog(root: Path, report: ValidationReport) -> tuple[set[str], set[str], dict[str, set[str]]]:
|
||||||
|
cap_types: set[str] = set()
|
||||||
|
iface_types: set[str] = set()
|
||||||
|
expected_ifaces: dict[str, set[str]] = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
cap_catalog = load_yaml(root / "catalog/capability-types.yaml")
|
||||||
|
for item in cap_catalog["spec"]["types"]:
|
||||||
|
cap_types.add(item["id"])
|
||||||
|
expected_ifaces[item["id"]] = set(item.get("expected_interface_types", []))
|
||||||
|
except Exception as exc:
|
||||||
|
report.add("ERROR", "catalog.load", f"cannot load capability catalog: {exc}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
iface_catalog = load_yaml(root / "catalog/interface-types.yaml")
|
||||||
|
for item in iface_catalog["spec"]["types"]:
|
||||||
|
iface_types.add(item["id"])
|
||||||
|
except Exception as exc:
|
||||||
|
report.add("ERROR", "catalog.load", f"cannot load interface catalog: {exc}")
|
||||||
|
|
||||||
|
return cap_types, iface_types, expected_ifaces
|
||||||
|
|
||||||
|
|
||||||
|
def _require_ref(
|
||||||
|
report: ValidationReport,
|
||||||
|
declaration: Declaration,
|
||||||
|
field: str,
|
||||||
|
collection: dict[str, Declaration],
|
||||||
|
value: Any,
|
||||||
|
) -> None:
|
||||||
|
if isinstance(value, str) and value in collection:
|
||||||
|
return
|
||||||
|
report.add("ERROR", "graph.missing_ref", f"{field} references unknown id {value!r}", declaration.path)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_dependency(
|
||||||
|
declaration: Declaration,
|
||||||
|
report: ValidationReport,
|
||||||
|
services: dict[str, Declaration],
|
||||||
|
capabilities: dict[str, Declaration],
|
||||||
|
interfaces: dict[str, Declaration],
|
||||||
|
cap_types: set[str],
|
||||||
|
iface_types: set[str],
|
||||||
|
expected_ifaces: dict[str, set[str]],
|
||||||
|
) -> None:
|
||||||
|
spec = declaration.spec
|
||||||
|
_require_ref(report, declaration, "consumer_service_id", services, spec.get("consumer_service_id"))
|
||||||
|
|
||||||
|
requires = spec.get("requires", {})
|
||||||
|
capability_type = requires.get("capability_type")
|
||||||
|
capability_id = requires.get("capability_id")
|
||||||
|
if capability_type not in cap_types:
|
||||||
|
report.add("ERROR", "catalog.unknown_capability_type", f"unknown required capability type {capability_type!r}", declaration.path)
|
||||||
|
if capability_id:
|
||||||
|
_require_ref(report, declaration, "requires.capability_id", capabilities, capability_id)
|
||||||
|
|
||||||
|
interface_type = spec.get("interface", {}).get("type")
|
||||||
|
if interface_type:
|
||||||
|
if interface_type not in iface_types:
|
||||||
|
report.add("ERROR", "catalog.unknown_interface_type", f"unknown dependency interface type {interface_type!r}", declaration.path)
|
||||||
|
expected = expected_ifaces.get(str(capability_type), set())
|
||||||
|
if expected and interface_type not in expected:
|
||||||
|
report.add(
|
||||||
|
"WARN",
|
||||||
|
"graph.unexpected_interface_type",
|
||||||
|
f"interface type {interface_type!r} is not expected for capability type {capability_type!r}",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
providers = _matching_providers(requires, capabilities)
|
||||||
|
if not providers:
|
||||||
|
report.add(
|
||||||
|
"ERROR",
|
||||||
|
"graph.missing_provider",
|
||||||
|
f"no provider capability found for {requires!r}",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
if _is_active_production(spec) and not declaration.metadata.get("source_links"):
|
||||||
|
report.add(
|
||||||
|
"ERROR",
|
||||||
|
"graph.missing_source_links",
|
||||||
|
"active production dependency requires metadata.source_links",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
if _is_active_production(spec):
|
||||||
|
viable = [provider for provider in providers if _provider_covers_dependency(provider.spec, spec)]
|
||||||
|
if providers and not viable:
|
||||||
|
report.add(
|
||||||
|
"ERROR",
|
||||||
|
"graph.incompatible_provider",
|
||||||
|
"no matching provider is active in the dependency environment",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_binding(
|
||||||
|
declaration: Declaration,
|
||||||
|
report: ValidationReport,
|
||||||
|
dependencies: dict[str, Declaration],
|
||||||
|
capabilities: dict[str, Declaration],
|
||||||
|
interfaces: dict[str, Declaration],
|
||||||
|
) -> None:
|
||||||
|
spec = declaration.spec
|
||||||
|
dependency = dependencies.get(str(spec.get("dependency_id")))
|
||||||
|
provider = capabilities.get(str(spec.get("provider_capability_id")))
|
||||||
|
_require_ref(report, declaration, "dependency_id", dependencies, spec.get("dependency_id"))
|
||||||
|
_require_ref(report, declaration, "provider_capability_id", capabilities, spec.get("provider_capability_id"))
|
||||||
|
if spec.get("provider_interface_id"):
|
||||||
|
_require_ref(report, declaration, "provider_interface_id", interfaces, spec.get("provider_interface_id"))
|
||||||
|
|
||||||
|
if dependency and provider:
|
||||||
|
required_type = dependency.spec.get("requires", {}).get("capability_type")
|
||||||
|
provider_type = provider.spec.get("capability_type")
|
||||||
|
if required_type != provider_type:
|
||||||
|
report.add(
|
||||||
|
"ERROR",
|
||||||
|
"graph.binding_type_mismatch",
|
||||||
|
f"binding provider type {provider_type!r} does not satisfy dependency type {required_type!r}",
|
||||||
|
declaration.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _matching_providers(requires: dict[str, Any], capabilities: dict[str, Declaration]) -> list[Declaration]:
|
||||||
|
capability_id = requires.get("capability_id")
|
||||||
|
if capability_id:
|
||||||
|
provider = capabilities.get(str(capability_id))
|
||||||
|
return [provider] if provider is not None else []
|
||||||
|
capability_type = requires.get("capability_type")
|
||||||
|
return [
|
||||||
|
declaration
|
||||||
|
for declaration in capabilities.values()
|
||||||
|
if declaration.spec.get("capability_type") == capability_type
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_active_production(spec: dict[str, Any]) -> bool:
|
||||||
|
environments = set(spec.get("environments", []))
|
||||||
|
return spec.get("lifecycle") == "active" and bool(environments & {"prod", "all"})
|
||||||
|
|
||||||
|
|
||||||
|
def _provider_covers_dependency(provider_spec: dict[str, Any], dependency_spec: dict[str, Any]) -> bool:
|
||||||
|
if provider_spec.get("lifecycle") != "active":
|
||||||
|
return False
|
||||||
|
provider_envs = set(provider_spec.get("environments", []))
|
||||||
|
dependency_envs = set(dependency_spec.get("environments", []))
|
||||||
|
if "all" in provider_envs:
|
||||||
|
return True
|
||||||
|
if "all" in dependency_envs:
|
||||||
|
return {"dev", "staging", "prod"}.issubset(provider_envs)
|
||||||
|
return bool(provider_envs & dependency_envs)
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_cycles(
|
||||||
|
dependencies: list[Declaration],
|
||||||
|
bindings: list[Declaration],
|
||||||
|
services: dict[str, Declaration],
|
||||||
|
capabilities: dict[str, Declaration],
|
||||||
|
report: ValidationReport,
|
||||||
|
) -> None:
|
||||||
|
dependency_by_id = {d.id: d for d in dependencies}
|
||||||
|
provider_by_dependency: dict[str, Declaration] = {}
|
||||||
|
for binding in bindings:
|
||||||
|
dep = str(binding.spec.get("dependency_id"))
|
||||||
|
provider_capability_id = str(binding.spec.get("provider_capability_id"))
|
||||||
|
provider = capabilities.get(provider_capability_id)
|
||||||
|
if dep and provider:
|
||||||
|
provider_by_dependency[dep] = provider
|
||||||
|
|
||||||
|
edges: dict[str, set[str]] = defaultdict(set)
|
||||||
|
for dep_id, provider in provider_by_dependency.items():
|
||||||
|
dependency = dependency_by_id.get(dep_id)
|
||||||
|
if dependency is None:
|
||||||
|
continue
|
||||||
|
consumer_service = dependency.spec.get("consumer_service_id")
|
||||||
|
provider_service = provider.spec.get("service_id")
|
||||||
|
if consumer_service in services and provider_service in services and consumer_service != provider_service:
|
||||||
|
edges[str(consumer_service)].add(str(provider_service))
|
||||||
|
|
||||||
|
visiting: set[str] = set()
|
||||||
|
visited: set[str] = set()
|
||||||
|
|
||||||
|
def visit(node: str, stack: list[str]) -> None:
|
||||||
|
if node in visiting:
|
||||||
|
cycle = stack[stack.index(node):] + [node]
|
||||||
|
report.add("WARN", "graph.cycle", "service dependency cycle: " + " -> ".join(cycle))
|
||||||
|
return
|
||||||
|
if node in visited:
|
||||||
|
return
|
||||||
|
visiting.add(node)
|
||||||
|
for target in edges.get(node, set()):
|
||||||
|
visit(target, stack + [target])
|
||||||
|
visiting.remove(node)
|
||||||
|
visited.add(node)
|
||||||
|
|
||||||
|
for node in sorted(edges):
|
||||||
|
visit(node, [node])
|
||||||
52
schemas/binding.schema.yaml
Normal file
52
schemas/binding.schema.yaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/binding.schema.yaml"
|
||||||
|
title: "BindingAssertion"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- metadata
|
||||||
|
- spec
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/apiVersion"
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
const: BindingAssertion
|
||||||
|
metadata:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/metadata"
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- lifecycle
|
||||||
|
- environments
|
||||||
|
- dependency_id
|
||||||
|
- provider_capability_id
|
||||||
|
- status
|
||||||
|
- rationale
|
||||||
|
properties:
|
||||||
|
lifecycle:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/lifecycle"
|
||||||
|
environments:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/environments"
|
||||||
|
dependency_id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
provider_capability_id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
provider_interface_id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- exact
|
||||||
|
- compatible
|
||||||
|
- degraded
|
||||||
|
- missing
|
||||||
|
- disputed
|
||||||
|
rationale:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
compatibility:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/compatibility"
|
||||||
52
schemas/capability.schema.yaml
Normal file
52
schemas/capability.schema.yaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/capability.schema.yaml"
|
||||||
|
title: "CapabilityDeclaration"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- metadata
|
||||||
|
- spec
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/apiVersion"
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
const: CapabilityDeclaration
|
||||||
|
metadata:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/metadata"
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- lifecycle
|
||||||
|
- environments
|
||||||
|
- description
|
||||||
|
- capability_type
|
||||||
|
- service_id
|
||||||
|
- criticality
|
||||||
|
- data_classification
|
||||||
|
properties:
|
||||||
|
lifecycle:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/lifecycle"
|
||||||
|
environments:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/environments"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
capability_type:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
service_id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
interface_ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
criticality:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/criticality"
|
||||||
|
data_classification:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/dataClassification"
|
||||||
|
compatibility:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/compatibility"
|
||||||
164
schemas/common.schema.yaml
Normal file
164
schemas/common.schema.yaml
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/common.schema.yaml"
|
||||||
|
title: "Railiance Fabric Common Definitions"
|
||||||
|
type: object
|
||||||
|
|
||||||
|
$defs:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
const: "railiance.fabric/v1alpha1"
|
||||||
|
|
||||||
|
graphId:
|
||||||
|
type: string
|
||||||
|
pattern: "^[a-z0-9][a-z0-9.-]*[a-z0-9]$"
|
||||||
|
minLength: 3
|
||||||
|
maxLength: 160
|
||||||
|
|
||||||
|
lifecycle:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- planned
|
||||||
|
- active
|
||||||
|
- deprecated
|
||||||
|
- retired
|
||||||
|
|
||||||
|
environment:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- dev
|
||||||
|
- staging
|
||||||
|
- prod
|
||||||
|
- all
|
||||||
|
|
||||||
|
environments:
|
||||||
|
type: array
|
||||||
|
minItems: 1
|
||||||
|
uniqueItems: true
|
||||||
|
items:
|
||||||
|
$ref: "#/$defs/environment"
|
||||||
|
|
||||||
|
criticality:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- low
|
||||||
|
- medium
|
||||||
|
- high
|
||||||
|
- critical
|
||||||
|
|
||||||
|
dataClassification:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- public
|
||||||
|
- internal
|
||||||
|
- confidential
|
||||||
|
- restricted
|
||||||
|
- secret
|
||||||
|
|
||||||
|
authMethod:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- none
|
||||||
|
- oidc
|
||||||
|
- jwt
|
||||||
|
- mtls
|
||||||
|
- kubernetes_service_account
|
||||||
|
- openbao_token
|
||||||
|
- static_secret
|
||||||
|
- database_role
|
||||||
|
- sts_token
|
||||||
|
- api_key
|
||||||
|
- unknown
|
||||||
|
|
||||||
|
sourceLink:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- label
|
||||||
|
properties:
|
||||||
|
label:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
ref:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
anyOf:
|
||||||
|
- required: [path]
|
||||||
|
- required: [url]
|
||||||
|
- required: [ref]
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- owner
|
||||||
|
- repo
|
||||||
|
- domain
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/$defs/graphId"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
repo:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
domain:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
source_links:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/$defs/sourceLink"
|
||||||
|
|
||||||
|
compatibility:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
requires:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
compatible_with:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/$defs/graphId"
|
||||||
|
breaks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
notes:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
|
||||||
|
auth:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- method
|
||||||
|
properties:
|
||||||
|
method:
|
||||||
|
$ref: "#/$defs/authMethod"
|
||||||
|
audience:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
scopes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
82
schemas/dependency.schema.yaml
Normal file
82
schemas/dependency.schema.yaml
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/dependency.schema.yaml"
|
||||||
|
title: "DependencyDeclaration"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- metadata
|
||||||
|
- spec
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/apiVersion"
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
const: DependencyDeclaration
|
||||||
|
metadata:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/metadata"
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- lifecycle
|
||||||
|
- environments
|
||||||
|
- consumer_service_id
|
||||||
|
- requires
|
||||||
|
- criticality
|
||||||
|
- data_classification
|
||||||
|
properties:
|
||||||
|
lifecycle:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/lifecycle"
|
||||||
|
environments:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/environments"
|
||||||
|
consumer_service_id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
requires:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- capability_type
|
||||||
|
properties:
|
||||||
|
capability_type:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
capability_id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
interface:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
version_constraint:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
auth:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/auth"
|
||||||
|
criticality:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/criticality"
|
||||||
|
data_classification:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/dataClassification"
|
||||||
|
fallback:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- mode
|
||||||
|
- description
|
||||||
|
properties:
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- none
|
||||||
|
- manual
|
||||||
|
- degraded
|
||||||
|
- cached
|
||||||
|
- alternate_provider
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
compatibility:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/compatibility"
|
||||||
69
schemas/interface.schema.yaml
Normal file
69
schemas/interface.schema.yaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/interface.schema.yaml"
|
||||||
|
title: "InterfaceDeclaration"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- metadata
|
||||||
|
- spec
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/apiVersion"
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
const: InterfaceDeclaration
|
||||||
|
metadata:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/metadata"
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- lifecycle
|
||||||
|
- environments
|
||||||
|
- description
|
||||||
|
- interface_type
|
||||||
|
- version
|
||||||
|
- service_id
|
||||||
|
- auth
|
||||||
|
- data_classification
|
||||||
|
properties:
|
||||||
|
lifecycle:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/lifecycle"
|
||||||
|
environments:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/environments"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
interface_type:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
service_id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
capability_ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
endpoint:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
notes:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
auth:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/auth"
|
||||||
|
data_classification:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/dataClassification"
|
||||||
|
compatibility:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/compatibility"
|
||||||
44
schemas/service.schema.yaml
Normal file
44
schemas/service.schema.yaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/service.schema.yaml"
|
||||||
|
title: "ServiceDeclaration"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- metadata
|
||||||
|
- spec
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/apiVersion"
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
const: ServiceDeclaration
|
||||||
|
metadata:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/metadata"
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- lifecycle
|
||||||
|
- environments
|
||||||
|
- description
|
||||||
|
properties:
|
||||||
|
lifecycle:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/lifecycle"
|
||||||
|
environments:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/environments"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
service_type:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
provides_capabilities:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
exposes_interfaces:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
71
schemas/state-hub-export.schema.yaml
Normal file
71
schemas/state-hub-export.schema.yaml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/state-hub-export.schema.yaml"
|
||||||
|
title: "FabricGraphExport"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- nodes
|
||||||
|
- edges
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
const: railiance.fabric/v1alpha1
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
const: FabricGraphExport
|
||||||
|
generated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
source:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
repo:
|
||||||
|
type: string
|
||||||
|
commit:
|
||||||
|
type: string
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
nodes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- kind
|
||||||
|
- name
|
||||||
|
- repo
|
||||||
|
- domain
|
||||||
|
- lifecycle
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
repo:
|
||||||
|
type: string
|
||||||
|
domain:
|
||||||
|
type: string
|
||||||
|
lifecycle:
|
||||||
|
type: string
|
||||||
|
edges:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- from
|
||||||
|
- to
|
||||||
|
- type
|
||||||
|
properties:
|
||||||
|
from:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
to:
|
||||||
|
$ref: "./common.schema.yaml#/$defs/graphId"
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
319
workplans/RAIL-FAB-WP-0001-ecosystem-graph-model.md
Normal file
319
workplans/RAIL-FAB-WP-0001-ecosystem-graph-model.md
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
---
|
||||||
|
id: RAIL-FAB-WP-0001
|
||||||
|
type: workplan
|
||||||
|
title: "Railiance Ecosystem Graph Model"
|
||||||
|
domain: railiance
|
||||||
|
repo: railiance-fabric
|
||||||
|
status: completed
|
||||||
|
owner: codex
|
||||||
|
topic_slug: railiance
|
||||||
|
planning_priority: high
|
||||||
|
planning_order: 1
|
||||||
|
state_hub_workstream_id: "bd190990-8e68-49a3-9ce4-0ba89103ea54"
|
||||||
|
created: "2026-05-17"
|
||||||
|
updated: "2026-05-17"
|
||||||
|
---
|
||||||
|
|
||||||
|
# RAIL-FAB-WP-0001 - Railiance Ecosystem Graph Model
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Define and implement the first useful version of the Railiance ecosystem
|
||||||
|
graph: a repo-owned declaration model for services, capabilities, interfaces,
|
||||||
|
and dependencies, plus tooling to validate, inspect, and export that graph.
|
||||||
|
|
||||||
|
The first outcome should be small but real: Railiance can declare that
|
||||||
|
OpenBao provides runtime secrets, NetKingdom provides identity/security
|
||||||
|
architecture, flex-auth provides authorization decisions, CNPG provides
|
||||||
|
PostgreSQL, and consumers can declare the exact capability/interface
|
||||||
|
requirements they rely on.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Railiance is moving from independent service setup into a connected ecosystem.
|
||||||
|
Platform services, identity, authorization, object storage, app workloads,
|
||||||
|
automation, and observability are starting to interact.
|
||||||
|
|
||||||
|
The current risk is that dependencies remain implicit: in docs, deployment
|
||||||
|
scripts, assumptions, and agent memory. That makes blast radius hard to see and
|
||||||
|
capability discovery fragile.
|
||||||
|
|
||||||
|
This workplan creates the foundation for a flexible ecosystem graph where:
|
||||||
|
|
||||||
|
- repos declare what they provide and consume
|
||||||
|
- capabilities are stable semantic contracts
|
||||||
|
- interfaces describe concrete integration surfaces
|
||||||
|
- dependencies capture consumer requirements
|
||||||
|
- bindings connect consumers to providers
|
||||||
|
- State Hub can ingest the result as a read model
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
In scope:
|
||||||
|
|
||||||
|
- define vocabulary for repo, service, capability, interface, dependency, and
|
||||||
|
binding
|
||||||
|
- define YAML schemas for repo-owned declarations
|
||||||
|
- create examples for current Railiance services and cross-domain integrations
|
||||||
|
- implement a local graph loader and validator
|
||||||
|
- implement basic discovery queries and graph export
|
||||||
|
- define State Hub ingestion shape without making State Hub the authoring
|
||||||
|
surface
|
||||||
|
- document adoption guidance for other repos
|
||||||
|
|
||||||
|
Out of scope:
|
||||||
|
|
||||||
|
- replacing State Hub workstream/task/capability catalog ownership
|
||||||
|
- deployment orchestration or GitOps
|
||||||
|
- service mesh runtime traffic discovery
|
||||||
|
- enforcing production rollout gates before the model is proven
|
||||||
|
- complete static code analysis across all repos
|
||||||
|
|
||||||
|
## Initial Model
|
||||||
|
|
||||||
|
Suggested declaration layout inside each participating repo:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fabric/
|
||||||
|
services/*.yaml
|
||||||
|
capabilities/*.yaml
|
||||||
|
interfaces/*.yaml
|
||||||
|
dependencies/*.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested edge model:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Repository
|
||||||
|
produces Service
|
||||||
|
provides Capability
|
||||||
|
exposes Interface
|
||||||
|
consumes Dependency
|
||||||
|
|
||||||
|
Dependency
|
||||||
|
requires Capability
|
||||||
|
optionally constrains Interface, version, environment, auth, tenant,
|
||||||
|
data_class, criticality, and fallback
|
||||||
|
|
||||||
|
Binding
|
||||||
|
resolves Dependency -> Capability/Interface
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### T01 - Intent And Vocabulary Baseline
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "7a74b4cf-6e0e-468c-8426-7dd85c027a21"
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `INTENT.md` and establish the initial vocabulary: repository, service,
|
||||||
|
capability, interface, dependency, and binding.
|
||||||
|
|
||||||
|
Done when the repo has a clear intent document that distinguishes repo-owned
|
||||||
|
declarations from State Hub's read-model role.
|
||||||
|
|
||||||
|
### T02 - Declaration Schema Design
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "eb4be5b3-7440-43ce-8af3-8da371cab8a5"
|
||||||
|
```
|
||||||
|
|
||||||
|
Define the first YAML schema set:
|
||||||
|
|
||||||
|
- `ServiceDeclaration`
|
||||||
|
- `CapabilityDeclaration`
|
||||||
|
- `InterfaceDeclaration`
|
||||||
|
- `DependencyDeclaration`
|
||||||
|
- `BindingOverride` or `BindingAssertion`
|
||||||
|
|
||||||
|
Include fields for:
|
||||||
|
|
||||||
|
- id, name, owner, repo, domain
|
||||||
|
- lifecycle status: planned, active, deprecated, retired
|
||||||
|
- environment: dev, staging, prod, all
|
||||||
|
- interface type and version
|
||||||
|
- auth method
|
||||||
|
- data classification
|
||||||
|
- criticality
|
||||||
|
- compatibility constraints
|
||||||
|
- source links
|
||||||
|
|
||||||
|
Done when schemas are documented and have example valid/invalid fixtures.
|
||||||
|
|
||||||
|
### T03 - Core Capability Type Catalog
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T03
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "070098b9-53b6-42d2-a041-e98b5c5ddcc7"
|
||||||
|
```
|
||||||
|
|
||||||
|
Define the first catalog of capability and interface types.
|
||||||
|
|
||||||
|
Initial capabilities:
|
||||||
|
|
||||||
|
- runtime secrets
|
||||||
|
- IAM Profile issuer
|
||||||
|
- authorization decision service
|
||||||
|
- PostgreSQL database service
|
||||||
|
- Redis-compatible cache
|
||||||
|
- object storage
|
||||||
|
- object-storage credential vending
|
||||||
|
- audit/event sink
|
||||||
|
- scope generation
|
||||||
|
|
||||||
|
Initial interface types:
|
||||||
|
|
||||||
|
- HTTP API
|
||||||
|
- OIDC discovery
|
||||||
|
- Kubernetes Secret
|
||||||
|
- Kubernetes CRD
|
||||||
|
- Helm release
|
||||||
|
- CLI
|
||||||
|
- database connection
|
||||||
|
- object-storage bucket
|
||||||
|
- event stream
|
||||||
|
- policy package
|
||||||
|
- OpenBao KV v2 mount
|
||||||
|
- OpenBao dynamic credential role
|
||||||
|
|
||||||
|
Done when the type catalog is explicit enough for examples to avoid ad hoc
|
||||||
|
strings.
|
||||||
|
|
||||||
|
### T04 - Example Declarations For Core Services
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T04
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "cfbc8d6e-58a3-4359-baf4-369d3357f6f5"
|
||||||
|
```
|
||||||
|
|
||||||
|
Create seed declarations for known Railiance ecosystem capabilities:
|
||||||
|
|
||||||
|
- OpenBao as `runtime-secrets`
|
||||||
|
- NetKingdom IAM Profile as identity contract
|
||||||
|
- key-cape as lightweight IAM Profile implementation
|
||||||
|
- flex-auth as authorization decision control plane
|
||||||
|
- Topaz as delegated PDP runtime
|
||||||
|
- CloudNativePG as PostgreSQL service
|
||||||
|
- Valkey as Redis-compatible cache
|
||||||
|
- object storage and STS credential vending as planned capabilities
|
||||||
|
- State Hub as coordination/read-model service
|
||||||
|
- repo-scoping as scope-generation provider
|
||||||
|
|
||||||
|
Done when the first graph has real provider nodes and at least a few consumer
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
### T05 - Graph Loader And Validator
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T05
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "59e46f73-04ba-464c-acbb-767169525d43"
|
||||||
|
```
|
||||||
|
|
||||||
|
Implement a local tool that loads declarations from one or more repo roots,
|
||||||
|
validates schema conformance, and builds an in-memory graph.
|
||||||
|
|
||||||
|
Validation should catch:
|
||||||
|
|
||||||
|
- duplicate ids
|
||||||
|
- missing provider capability for required dependencies
|
||||||
|
- unknown interface or capability types
|
||||||
|
- incompatible environment/status constraints
|
||||||
|
- missing source links for active production dependencies
|
||||||
|
- circular dependency warnings where relevant
|
||||||
|
|
||||||
|
Done when `railiance-fabric validate <repo...>` can run against the seed
|
||||||
|
examples and produce useful diagnostics.
|
||||||
|
|
||||||
|
### T06 - Discovery Queries And Exports
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T06
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "ee50217c-8cdc-4c18-bb15-9fed35599d6c"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add initial query commands:
|
||||||
|
|
||||||
|
- list providers for a capability
|
||||||
|
- list consumers of a capability/interface
|
||||||
|
- show dependency path for a service
|
||||||
|
- show missing or unresolved dependencies
|
||||||
|
- show blast radius for an interface version change
|
||||||
|
- export graph as JSON
|
||||||
|
- export graph as Mermaid for documentation
|
||||||
|
|
||||||
|
Done when a human or agent can answer "who consumes OpenBao?" and "what
|
||||||
|
capabilities are missing?" from local files.
|
||||||
|
|
||||||
|
### T07 - State Hub Integration Contract
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T07
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "32587d7a-565d-4457-a7ed-61e6f16f781a"
|
||||||
|
```
|
||||||
|
|
||||||
|
Define how State Hub ingests Railiance Fabric graph exports.
|
||||||
|
|
||||||
|
The integration contract must preserve the source-of-truth boundary:
|
||||||
|
|
||||||
|
- repo declarations are authoritative
|
||||||
|
- Railiance Fabric validates and exports
|
||||||
|
- State Hub stores or displays the read model
|
||||||
|
- State Hub links graph nodes to topics, repos, workstreams, tasks, and
|
||||||
|
progress events where useful
|
||||||
|
|
||||||
|
Done when there is a documented export shape and a proposed ingestion path for
|
||||||
|
State Hub.
|
||||||
|
|
||||||
|
### T08 - Adoption Guide And First Repo Rollout
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0001-T08
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "c13b29a7-053c-4f16-ab71-75fba966897e"
|
||||||
|
```
|
||||||
|
|
||||||
|
Write an adoption guide and apply the first declarations to a small set of
|
||||||
|
repos.
|
||||||
|
|
||||||
|
Suggested first rollout:
|
||||||
|
|
||||||
|
- `railiance-platform`
|
||||||
|
- `net-kingdom`
|
||||||
|
- `flex-auth`
|
||||||
|
- `artifact-store`
|
||||||
|
- `repo-scoping`
|
||||||
|
- `the-custodian/state-hub`
|
||||||
|
|
||||||
|
Done when another repo can add declarations without reading Railiance Fabric
|
||||||
|
source code.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `INTENT.md` clearly defines why Railiance Fabric exists.
|
||||||
|
- The repo contains a documented schema for services, capabilities,
|
||||||
|
interfaces, dependencies, and bindings.
|
||||||
|
- Seed declarations represent at least OpenBao, NetKingdom IAM, flex-auth,
|
||||||
|
CNPG, object storage, and State Hub.
|
||||||
|
- A local validation command can detect missing providers and type mistakes.
|
||||||
|
- A local query can list providers and consumers for a capability.
|
||||||
|
- A graph export can be consumed by State Hub without making State Hub the
|
||||||
|
authoring surface.
|
||||||
|
- Adoption guidance exists for adding declarations to other repos.
|
||||||
184
workplans/RAIL-FAB-WP-0002-ecosystem-registry-service.md
Normal file
184
workplans/RAIL-FAB-WP-0002-ecosystem-registry-service.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
---
|
||||||
|
id: RAIL-FAB-WP-0002
|
||||||
|
type: workplan
|
||||||
|
title: "Railiance Ecosystem Registry Service"
|
||||||
|
domain: railiance
|
||||||
|
repo: railiance-fabric
|
||||||
|
status: proposed
|
||||||
|
owner: codex
|
||||||
|
topic_slug: railiance
|
||||||
|
planning_priority: high
|
||||||
|
planning_order: 2
|
||||||
|
created: "2026-05-17"
|
||||||
|
updated: "2026-05-17"
|
||||||
|
---
|
||||||
|
|
||||||
|
# RAIL-FAB-WP-0002 - Railiance Ecosystem Registry Service
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Create the first service interface for registering repositories and interacting
|
||||||
|
with the Railiance ecosystem model across repos.
|
||||||
|
|
||||||
|
The service should make Fabric declarations queryable without requiring every
|
||||||
|
agent or dashboard to clone every repo and run the local CLI. It should remain
|
||||||
|
an indexed read model over repo-owned declarations, not a central editor for
|
||||||
|
those declarations.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
RAIL-FAB-WP-0001 created the repo-owned graph declaration model, validator,
|
||||||
|
query CLI, and State Hub export contract.
|
||||||
|
|
||||||
|
The next useful step is a service that can:
|
||||||
|
|
||||||
|
- register participating repos
|
||||||
|
- ingest validated graph snapshots by repo and commit
|
||||||
|
- expose providers, consumers, unresolved dependencies, dependency paths, and
|
||||||
|
blast-radius queries over the combined ecosystem graph
|
||||||
|
- attach supporting artifacts such as CycloneDX SBOMs, OpenAPI contracts,
|
||||||
|
AsyncAPI contracts, and Score workload intent
|
||||||
|
- project graph data to State Hub and, later, Backstage or xRegistry-compatible
|
||||||
|
views
|
||||||
|
|
||||||
|
## Direction
|
||||||
|
|
||||||
|
Use `docs/ecosystem-registry-service.md` as the design baseline.
|
||||||
|
|
||||||
|
The closest external comparison point is CNCF xRegistry because it defines an
|
||||||
|
extensible metadata registry model with document/API views, versioned resources,
|
||||||
|
filtering, import/export, and endpoint/schema/message extensions. Railiance
|
||||||
|
should keep an xRegistry-compatible projection path, while preserving the
|
||||||
|
Fabric graph model as the repo-native source of truth.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
In scope:
|
||||||
|
|
||||||
|
- define the registry service API and storage model
|
||||||
|
- implement repository registration
|
||||||
|
- implement snapshot ingestion for `FabricGraphExport`
|
||||||
|
- reuse the existing Fabric loader, validator, graph builder, and query logic
|
||||||
|
- store validation results per repo and commit
|
||||||
|
- expose initial HTTP query endpoints matching current CLI queries
|
||||||
|
- expose State Hub export data from the latest accepted snapshots
|
||||||
|
- support artifact attachment metadata for OpenAPI, AsyncAPI, and CycloneDX
|
||||||
|
- document Backstage and xRegistry projection strategy
|
||||||
|
|
||||||
|
Out of scope:
|
||||||
|
|
||||||
|
- editing repo-owned `fabric/` declarations through the service
|
||||||
|
- provisioning or binding live infrastructure
|
||||||
|
- replacing State Hub planning, task, progress, or workplan state
|
||||||
|
- building a full developer portal
|
||||||
|
- runtime service mesh discovery
|
||||||
|
- mandatory Score adoption
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### T01 - Service API And Storage Design
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T01
|
||||||
|
status: proposed
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Define the API surface, storage tables, validation semantics, and snapshot
|
||||||
|
replacement rules.
|
||||||
|
|
||||||
|
Done when the repo contains an implementation-ready service design that
|
||||||
|
identifies request/response shapes and storage ownership.
|
||||||
|
|
||||||
|
### T02 - Service Scaffold
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T02
|
||||||
|
status: proposed
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a lightweight HTTP service that reuses the existing Python loader,
|
||||||
|
validator, graph builder, and export model.
|
||||||
|
|
||||||
|
Done when the service can start locally and expose a health endpoint.
|
||||||
|
|
||||||
|
### T03 - Repository Registration
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T03
|
||||||
|
status: proposed
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Add endpoints and storage for repository slug, repo URL, default branch,
|
||||||
|
optional State Hub repo id, and ingest configuration.
|
||||||
|
|
||||||
|
Done when repos can be registered, listed, and fetched by slug.
|
||||||
|
|
||||||
|
### T04 - Snapshot Ingestion
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T04
|
||||||
|
status: proposed
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Add atomic ingestion for `FabricGraphExport` payloads keyed by repo and commit.
|
||||||
|
|
||||||
|
Done when a valid export is accepted, invalid exports are rejected with useful
|
||||||
|
errors, and the latest accepted snapshot is queryable.
|
||||||
|
|
||||||
|
### T05 - Ecosystem Query Endpoints
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T05
|
||||||
|
status: proposed
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Expose providers, consumers, unresolved dependencies, dependency paths, and
|
||||||
|
blast-radius queries over the latest accepted snapshots.
|
||||||
|
|
||||||
|
Done when HTTP responses match the local CLI answers for the same graph.
|
||||||
|
|
||||||
|
### T06 - Artifact Attachment
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T06
|
||||||
|
status: proposed
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Support artifact metadata for CycloneDX SBOMs, OpenAPI contracts, AsyncAPI
|
||||||
|
contracts, and Score workload files.
|
||||||
|
|
||||||
|
Done when artifacts can be linked to repos, services, or interfaces and surfaced
|
||||||
|
in graph node details.
|
||||||
|
|
||||||
|
### T07 - State Hub Export
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T07
|
||||||
|
status: proposed
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Expose State Hub export data from the registry's latest accepted snapshots.
|
||||||
|
|
||||||
|
Done when State Hub can fetch the same graph shape documented in
|
||||||
|
`docs/state-hub-integration.md`.
|
||||||
|
|
||||||
|
### T08 - Projection Strategy
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: RAIL-FAB-WP-0002-T08
|
||||||
|
status: proposed
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Document and, if small enough, prototype Backstage and xRegistry projections.
|
||||||
|
|
||||||
|
Done when it is clear which Fabric nodes map to Backstage entities and which
|
||||||
|
parts of the registry can be exposed through xRegistry-style groups, resources,
|
||||||
|
and versions.
|
||||||
Reference in New Issue
Block a user