generated from coulomb/repo-seed
Compare commits
79 Commits
5e0ffea585
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cbfb3d0f74 | |||
| 20316c533f | |||
| 58a2cfac5c | |||
| 7312ed3767 | |||
| f9e661ec69 | |||
| f57a5c1dce | |||
| 757770b42f | |||
| 635521406f | |||
| 2afeb86ed2 | |||
| caa1e4100d | |||
| dae9e3409a | |||
| e23ed0c06b | |||
| fe2e98e2a4 | |||
| 7cb943ed41 | |||
| 31841d0f1c | |||
| 40b409cd92 | |||
| 0b5d295800 | |||
| 5dff7f14da | |||
| 5e89f6a075 | |||
| f09f110e77 | |||
| 558e0dc157 | |||
| 0f7b7d1fed | |||
| 9b612447ca | |||
| a7a22a673f | |||
| 296ac051a7 | |||
| 1ad432270b | |||
| f1cd74dc7b | |||
| d060b1c896 | |||
| fdee0b0855 | |||
| f41d0da831 | |||
| 5a2d987b6a | |||
| ea81533172 | |||
| c26a725f92 | |||
| 8176d88926 | |||
| ff1c4ce05b | |||
| 62236f6453 | |||
| d9f1f3f71a | |||
| 5c9c2f281e | |||
| ea2fa1203b | |||
| e60e8d5bb4 | |||
| 735867392e | |||
| 7956415924 | |||
| 355b7be66a | |||
| 071a4c49e3 | |||
| a55f1a45d6 | |||
| c27d71a511 | |||
| ab7e0ccab1 | |||
| 26f1913d51 | |||
| 999f90dcbe | |||
| 43d3866b18 | |||
| a1bd9df8e4 | |||
| 455362153d | |||
| f4aa4aa996 | |||
| 9717b18b02 | |||
| 13188a6ae1 | |||
| 7d9b49764b | |||
| 63379ca329 | |||
| be0299e528 | |||
| 3a47a92729 | |||
| 3318d2c1b9 | |||
| 39762551c0 | |||
| 0874446994 | |||
| 7db9c3cbf2 | |||
| a1e99d9b34 | |||
| b70e74fde5 | |||
| d3fe1e3ed3 | |||
| fd859998c4 | |||
| 3e181e58ff | |||
| f6dbf31db1 | |||
| 9c22d3e0df | |||
| 653411ffb8 | |||
| 0193c97094 | |||
| 01bc4f3efe | |||
| 072fa8f7a7 | |||
| a8e67db9e9 | |||
| af4132c182 | |||
| 78e8d381c2 | |||
| e3440df163 | |||
| 36d4b895f9 |
50
.claude/rules/credential-routing.md
Normal file
50
.claude/rules/credential-routing.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Credential and access routing
|
||||
|
||||
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||
|
||||
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||
other credential need belongs to another subsystem. **Do not** message
|
||||
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||
|
||||
### Lookup (do this first)
|
||||
|
||||
```bash
|
||||
warden route find "<describe your need>" --json
|
||||
warden route show <catalog-id> --json
|
||||
```
|
||||
|
||||
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||
|
||||
| Agent runtime | How to orient |
|
||||
| --- | --- |
|
||||
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=railiance-fabric` is for coordination, not secret vending |
|
||||
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
|
||||
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||
|
||||
### Quick routing table
|
||||
|
||||
| I need… | Owner | ops-warden executes? |
|
||||
| --- | --- | --- |
|
||||
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||
| Authorization decision | flex-auth | No — route only |
|
||||
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||
|
||||
### Anti-patterns (do not do these)
|
||||
|
||||
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||
|
||||
### Other capabilities (reuse-surface)
|
||||
|
||||
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||
get wrong.
|
||||
|
||||
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||
@@ -1,37 +1,41 @@
|
||||
## First Session Protocol
|
||||
|
||||
Triggered when `get_domain_summary("railiance")` shows **no workstreams**.
|
||||
Triggered when `get_domain_summary("financials")` shows **no workplans**.
|
||||
The project is registered but work has not yet been structured.
|
||||
|
||||
**Step 1 — Read, don't write**
|
||||
- `~/the-custodian/canon/projects/railiance/project_charter_v0.1.md` — purpose, scope
|
||||
- `~/the-custodian/canon/projects/railiance/roadmap_v0.1.md` — planned phases
|
||||
- `~/the-custodian/canon/projects/financials/project_charter_v0.1.md` — purpose, scope
|
||||
- `~/the-custodian/canon/projects/financials/roadmap_v0.1.md` — planned phases
|
||||
- Scan repo root: README, directory structure, existing code or docs
|
||||
|
||||
**Step 2 — Survey in-progress work**
|
||||
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
|
||||
|
||||
**Step 3 — Propose workstreams to Bernd**
|
||||
Propose 1–3 workstreams — each a coherent strand, weeks to months, anchored to a
|
||||
**Step 3 — Propose workplans to Bernd**
|
||||
Propose 1–3 workplans — each a coherent strand, weeks to months, anchored to a
|
||||
roadmap phase. **Wait for approval before creating.**
|
||||
|
||||
**Step 4 — Create workplan file first, then DB record (ADR-001)**
|
||||
**Step 4 — Write the workplan file; fix-consistency registers it (ADR-001)**
|
||||
```
|
||||
workplans/railiance-fabric-WP-NNNN-<slug>.md ← write this first
|
||||
workplans/RAILIANCE-WP-NNNN-<slug>.md ← write this, commit it
|
||||
```
|
||||
Then register in the hub:
|
||||
```
|
||||
create_workstream(topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38", title="...", owner="...", description="...")
|
||||
create_task(workstream_id="<id>", title="...", priority="high|medium|low")
|
||||
Then register by running the consistency check — do **not** call
|
||||
`create_workplan`/`create_task` (or legacy `create_workstream`) yourself;
|
||||
manual registration duplicates what C-06 creates from the file:
|
||||
```bash
|
||||
statehub fix-consistency --repo railiance-fabric
|
||||
```
|
||||
C-06 creates the hub workplan + tasks and writes `state_hub_workstream_id` /
|
||||
`state_hub_task_id` back into the file (legacy field names, kept for
|
||||
compatibility — they hold workplan/task IDs).
|
||||
|
||||
**Step 5 — Record the setup**
|
||||
```
|
||||
add_progress_event(
|
||||
summary="First session: structured railiance into N workstreams, M tasks",
|
||||
summary="First session: structured financials into N workplans, M tasks",
|
||||
event_type="milestone",
|
||||
topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38",
|
||||
detail={"workstreams": [...], "tasks_created": M}
|
||||
detail={"workplans": [...], "tasks_created": M}
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
**Purpose:** railiance-fabric - (fill in purpose)
|
||||
|
||||
**Domain:** railiance
|
||||
**Domain:** financials
|
||||
**Repo slug:** railiance-fabric
|
||||
**Topic ID:** ca369340-a64e-442e-98f1-a4fa7dc74a38
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## Session Protocol
|
||||
|
||||
State Hub: http://127.0.0.1:8000
|
||||
Dev Hub (State Hub API): http://127.0.0.1:8000
|
||||
MCP server name in `~/.claude.json`: `dev-hub`
|
||||
|
||||
**Step 1 — Orient**
|
||||
|
||||
@@ -10,7 +11,7 @@ cat .custodian-brief.md
|
||||
```
|
||||
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
|
||||
```
|
||||
get_domain_summary("railiance")
|
||||
get_domain_summary("financials")
|
||||
```
|
||||
If MCP tools are unavailable in the current agent session, use the REST API:
|
||||
```bash
|
||||
@@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||
ls workplans/
|
||||
```
|
||||
For each file with `status: ready`, `active`, or `blocked`, note pending
|
||||
`todo`/`in_progress` tasks.
|
||||
`wait`/`todo`/`progress` tasks.
|
||||
|
||||
**Step 4 — Present brief**
|
||||
|
||||
1. **Active workstreams** for `railiance` — title, task counts, blocking decisions
|
||||
1. **Active workplans** for `financials` — title, task counts, blocking decisions
|
||||
2. **Pending tasks** from `workplans/` + any `[repo:railiance-fabric]` hub tasks
|
||||
3. **Goal guidance** — if `goal_guidance` in summary:
|
||||
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
|
||||
@@ -51,33 +52,42 @@ For each file with `status: ready`, `active`, or `blocked`, note pending
|
||||
4. **Suggested next action** — highest-priority open item
|
||||
5. **SBOM status** — flag if `last_sbom_at` is unset for this repo
|
||||
|
||||
If no workstreams: follow First Session Protocol (`first-session.md`).
|
||||
If no workplans: follow First Session Protocol (`first-session.md`).
|
||||
|
||||
**During work:** `record_decision()` · `add_progress_event()` · `resolve_decision()`
|
||||
|
||||
> State Hub is a *read model*. Bootstrap tools (`create_workstream`, `create_task`)
|
||||
> are First Session Protocol only. Work structure belongs in repo files (ADR-001).
|
||||
> State Hub is a *read model*. **Never register workplans or tasks by hand**
|
||||
> (`create_workplan`, `create_task`, or the legacy `create_workstream`) — write
|
||||
> the workplan file in `workplans/` and run `fix-consistency`; its C-06 check
|
||||
> registers the workplan and its tasks in the hub and writes the IDs back into
|
||||
> the file. Manual registration creates duplicates the moment fix-consistency
|
||||
> runs. Work structure belongs in repo files (ADR-001).
|
||||
>
|
||||
> Terminology: "workstream" is the legacy name for workplan. Some API/frontmatter
|
||||
> field names keep it for compatibility (`state_hub_workstream_id`,
|
||||
> `workstream_id` params) — treat them as workplan IDs.
|
||||
|
||||
**Session close:**
|
||||
With MCP tools:
|
||||
```
|
||||
add_progress_event(summary="...", topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38", workstream_id="<uuid>")
|
||||
add_progress_event(summary="...", topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38", workplan_id="<uuid>")
|
||||
```
|
||||
Without MCP tools:
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"topic_id":"ca369340-a64e-442e-98f1-a4fa7dc74a38","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
|
||||
-d '{"topic_id":"ca369340-a64e-442e-98f1-a4fa7dc74a38","workplan_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
|
||||
```
|
||||
If workplan files were modified, ensure the local copy is up to date first:
|
||||
If workplan files were modified, ensure the local copy is up to date first,
|
||||
then sync from the repo checkout:
|
||||
```bash
|
||||
git -C <repo_path> pull --ff-only
|
||||
cd ~/state-hub && make fix-consistency REPO=railiance-fabric
|
||||
git pull --ff-only
|
||||
statehub fix-consistency
|
||||
```
|
||||
For repos where implementation runs on a remote machine (e.g. CoulombCore),
|
||||
use the combined target which pulls before fixing:
|
||||
use the pull-before-fix mode from any shell with the State Hub CLI:
|
||||
```bash
|
||||
cd ~/state-hub && make fix-consistency-remote REPO=railiance-fabric
|
||||
statehub fix-consistency --repo railiance-fabric --remote
|
||||
```
|
||||
**C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback
|
||||
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
## Stack
|
||||
|
||||
<!-- TODO: Fill in language, frameworks, and key dependencies -->
|
||||
- **Language:**
|
||||
- **Key deps:**
|
||||
- **Language:** Python ≥3.12 (`railiance_fabric` package)
|
||||
- **Key deps:** jsonschema, PyYAML; SQLite registry at `.railiance-fabric/registry.sqlite3`
|
||||
|
||||
## Dev Commands
|
||||
|
||||
```bash
|
||||
# TODO: Fill in the standard commands for this repo
|
||||
|
||||
# Install dependencies
|
||||
|
||||
# Run tests
|
||||
|
||||
# Lint / type check
|
||||
|
||||
# Build / package (if applicable)
|
||||
python3 -m pytest tests/ # run the test suite
|
||||
make graph-explorer # registry-backed graph explorer (HOST/PORT/REGISTRY_DB overridable)
|
||||
python3 -m railiance_fabric.server --db .railiance-fabric/registry.sqlite3
|
||||
```
|
||||
|
||||
Local-only tooling — no production deploy surface in this repo.
|
||||
|
||||
@@ -1,28 +1,45 @@
|
||||
## Workplan Convention (ADR-001)
|
||||
|
||||
File location: `workplans/railiance-fabric-WP-NNNN-<slug>.md`
|
||||
ID prefix: `RAILIANCE-WP`
|
||||
File location: `workplans/RAILIANCE-WP-NNNN-<slug>.md`
|
||||
ID prefix: `RAILIANCE-WP-`
|
||||
|
||||
Work items originate as files in this repo **before** being registered in the hub.
|
||||
|
||||
Canonical workplan/workstream frontmatter statuses are:
|
||||
Canonical workplan frontmatter statuses are:
|
||||
`proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, `archived`.
|
||||
Use `proposed` for a newly drafted plan, `ready` after review against current
|
||||
repo state, and `finished` when implementation is complete. `stalled` and
|
||||
`needs_review` are derived health labels, not stored statuses.
|
||||
|
||||
Closed workplans may be moved to `workplans/archived/` with a completion-date
|
||||
prefix: `YYMMDD-railiance-fabric-WP-NNNN-<slug>.md`. The frontmatter id remains
|
||||
prefix: `YYMMDD-RAILIANCE-WP-NNNN-<slug>.md`. The frontmatter id remains
|
||||
unchanged; the prefix is only for quick visual reference.
|
||||
|
||||
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
||||
`workplans/ADHOC-YYYY-MM-DD.md`, workstream slug `adhoc-YYYY-MM-DD`, and task ids
|
||||
`workplans/ADHOC-YYYY-MM-DD.md`, workplan slug `adhoc-YYYY-MM-DD`, and task ids
|
||||
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
|
||||
directly. Promote anything requiring analysis, design, approval, dependencies, or
|
||||
multiple planned phases into a normal workplan.
|
||||
|
||||
Ecosystem todos from other agents arrive as `[repo:railiance-fabric]` hub tasks —
|
||||
visible at session start. Pick one up by creating the workplan file, then registering
|
||||
the workstream.
|
||||
visible at session start. Pick one up by creating the workplan file, committing,
|
||||
and running `statehub fix-consistency` — C-06 registers the workplan in the hub.
|
||||
Never register by hand with `create_workplan`/`create_workstream`.
|
||||
|
||||
Task blocks use this shape:
|
||||
|
||||
```task
|
||||
id: RAILIANCE-WP-NNNN-T01
|
||||
status: wait | todo | progress | done | cancel
|
||||
priority: high | medium | low
|
||||
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||
```
|
||||
|
||||
Status progression is `todo` → `progress` → `done`; use `wait` for waiting or
|
||||
blocked work and `cancel` for stopped work.
|
||||
|
||||
Workplan frontmatter carries `state_hub_workstream_id` — a legacy field name
|
||||
kept for compatibility ("workstream" is the old term for workplan); it holds
|
||||
the hub workplan id and is written by fix-consistency. Do not edit or rename it.
|
||||
|
||||
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||
# Custodian Brief — railiance-fabric
|
||||
|
||||
**Domain:** railiance
|
||||
**Last synced:** 2026-05-20 21:37 UTC
|
||||
**Domain:** financials
|
||||
**Last synced:** 2026-07-01 22:24 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### Path Scoped Duplicate Identity
|
||||
Progress: 0/4 done | workstream_id: `bf80900b-606c-442c-aa15-d6272d73a8d6`
|
||||
|
||||
**Open tasks:**
|
||||
- · T01 - Add Path-Scoped Reconciliation Coverage `8d9cb1c2`
|
||||
- · T02 - Implement Path-Aware Duplicate Detection `7d4d439d`
|
||||
- · T03 - Verify Controlled Rescan `0d6cad1d`
|
||||
- · T04 - Rollout Recommendation `4be31147`
|
||||
*(none — repo may need first-session setup)*
|
||||
|
||||
---
|
||||
## MCP Orientation (when available)
|
||||
|
||||
If the state-hub MCP server is reachable, call:
|
||||
`get_domain_summary("railiance")`
|
||||
`get_domain_summary("financials")`
|
||||
This provides richer cross-domain context.
|
||||
If the MCP call fails, use this file as your orientation source.
|
||||
|
||||
17
.repo-classification.yaml
Normal file
17
.repo-classification.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
repo_classification:
|
||||
standard: Repo Classification Standard
|
||||
version: '1.0'
|
||||
classified_at: '2026-06-22'
|
||||
classified_by: agent
|
||||
category: project
|
||||
domain: financials
|
||||
secondary_domains: []
|
||||
capability_tags:
|
||||
- platform
|
||||
- operations
|
||||
business_stake:
|
||||
- technology
|
||||
- operations
|
||||
business_mechanics:
|
||||
- coordination
|
||||
- operation
|
||||
95
AGENTS.md
95
AGENTS.md
@@ -4,7 +4,7 @@
|
||||
|
||||
**Purpose:** railiance-fabric - (fill in purpose)
|
||||
|
||||
**Domain:** railiance
|
||||
**Domain:** financials
|
||||
**Repo slug:** railiance-fabric
|
||||
**Topic ID:** `ca369340-a64e-442e-98f1-a4fa7dc74a38`
|
||||
**Workplan prefix:** `RAILIANCE-WP-`
|
||||
@@ -20,6 +20,12 @@ there is no MCP server for Codex agents.
|
||||
|---------|-----|
|
||||
| Local workstation | `http://127.0.0.1:8000` |
|
||||
| Remote via tunnel | `http://127.0.0.1:18000` |
|
||||
| Optional local edge relay | http://127.0.0.1:18080 |
|
||||
|
||||
When an operator has enabled the edge relay, set API_BASE to the relay URL.
|
||||
Queueable writes return an explicit queued receipt if the central hub is
|
||||
unreachable. Treat that as pending local evidence, then ask the operator to run
|
||||
statehub outbox status/replay after connectivity returns.
|
||||
|
||||
### Orient at session start
|
||||
|
||||
@@ -27,8 +33,8 @@ there is no MCP server for Codex agents.
|
||||
# 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" \
|
||||
# Active workplans for this domain
|
||||
curl -s "http://127.0.0.1:8000/workplans/?topic_id=ca369340-a64e-442e-98f1-a4fa7dc74a38&status=active" \
|
||||
| python3 -m json.tool
|
||||
|
||||
# Check inbox
|
||||
@@ -51,20 +57,20 @@ curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||
"summary": "what was done",
|
||||
"event_type": "note",
|
||||
"author": "codex",
|
||||
"workstream_id": "<uuid>",
|
||||
"workplan_id": "<uuid>",
|
||||
"task_id": "<uuid>"
|
||||
}'
|
||||
```
|
||||
|
||||
Omit `workstream_id` / `task_id` when not applicable.
|
||||
Omit `workplan_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
|
||||
-d '{"status": "progress"}'
|
||||
# values: wait | todo | progress | done | cancel
|
||||
```
|
||||
|
||||
### Flag a task for human review
|
||||
@@ -80,10 +86,10 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
## Session Protocol
|
||||
|
||||
**Start:**
|
||||
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
|
||||
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
|
||||
2. Check inbox: `GET /messages/?to_agent=railiance-fabric&unread_only=true`; mark read
|
||||
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
|
||||
4. Check blocked tasks: `GET /tasks/?needs_human=true`
|
||||
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
|
||||
|
||||
**During work:**
|
||||
- Update task statuses in workplan files as tasks progress
|
||||
@@ -92,12 +98,69 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
**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
|
||||
`~/state-hub`:
|
||||
3. After workplan file changes, run:
|
||||
```bash
|
||||
make fix-consistency REPO=railiance-fabric
|
||||
statehub fix-consistency
|
||||
```
|
||||
This syncs task status from files into the hub DB.
|
||||
Coding agents should run this directly; ask the operator only if the CLI or
|
||||
State Hub API is unavailable. This syncs task status from files into the hub DB.
|
||||
|
||||
---
|
||||
|
||||
## Credential and access routing
|
||||
|
||||
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||
|
||||
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||
other credential need belongs to another subsystem. **Do not** message
|
||||
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||
|
||||
### Lookup (do this first)
|
||||
|
||||
```bash
|
||||
warden route find "<describe your need>" --json
|
||||
warden route show <catalog-id> --json
|
||||
```
|
||||
|
||||
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||
|
||||
| Agent runtime | How to orient |
|
||||
| --- | --- |
|
||||
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=railiance-fabric` is for coordination, not secret vending |
|
||||
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
|
||||
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||
|
||||
### Quick routing table
|
||||
|
||||
| I need… | Owner | ops-warden executes? |
|
||||
| --- | --- | --- |
|
||||
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||
| Authorization decision | flex-auth | No — route only |
|
||||
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||
|
||||
### Anti-patterns (do not do these)
|
||||
|
||||
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||
|
||||
### Other capabilities (reuse-surface)
|
||||
|
||||
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||
get wrong.
|
||||
|
||||
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||
|
||||
<!-- REPO-AGENTS-EXTENSIONS -->
|
||||
<!-- Append repo-specific agent instructions below this marker.
|
||||
The state-hub template sync preserves content after this line. -->
|
||||
|
||||
---
|
||||
|
||||
@@ -124,7 +187,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
|
||||
id: RAILIANCE-WP-NNNN
|
||||
type: workplan
|
||||
title: "..."
|
||||
domain: railiance
|
||||
domain: financials
|
||||
repo: railiance-fabric
|
||||
status: proposed | ready | active | blocked | backlog | finished | archived
|
||||
owner: codex
|
||||
@@ -146,7 +209,7 @@ derived health labels, not frontmatter statuses.
|
||||
|
||||
` ` `task
|
||||
id: RAILIANCE-WP-NNNN-T01
|
||||
status: todo | in_progress | done | blocked
|
||||
status: wait | todo | progress | done | cancel
|
||||
priority: high | medium | low
|
||||
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||
` ` `
|
||||
@@ -154,7 +217,7 @@ state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||
Task description text.
|
||||
```
|
||||
|
||||
Status progression: `todo` → `in_progress` → `done` (or `blocked`)
|
||||
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
|
||||
|
||||
To create a new workplan:
|
||||
1. Write the file following the format above
|
||||
|
||||
@@ -8,4 +8,5 @@
|
||||
@.claude/rules/stack-and-commands.md
|
||||
@.claude/rules/architecture.md
|
||||
@.claude/rules/repo-boundary.md
|
||||
@.claude/rules/credential-routing.md
|
||||
@.claude/rules/agents.md
|
||||
|
||||
93
DECISIONS.md
Normal file
93
DECISIONS.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Decision Log
|
||||
|
||||
_Auto-generated by the Custodian State Hub._
|
||||
|
||||
## State Hub workstation Postgres migration approval
|
||||
|
||||
**Date:** 2026-06-03
|
||||
**Decided by:** codex
|
||||
|
||||
Superseded by WP-0004 T09 cancellation and CUST-WP-0038 ownership of State Hub HA migration, including hostname, registry, exposure model, HA database/storage, restore drills, and production data migration approval.
|
||||
|
||||
---
|
||||
|
||||
## activity-core deployment architecture
|
||||
|
||||
**Date:** 2026-06-03
|
||||
**Decided by:** codex
|
||||
|
||||
Superseded by completed WP-0004 outcome: activity-core was deployed as a K3s production service on railiance01 on 2026-05-22; the packaging architecture question is no longer blocking this workplan.
|
||||
|
||||
---
|
||||
|
||||
## Review CCR-2026-0001 whynot-design npm publish token lane
|
||||
|
||||
**Date:** 2026-06-27
|
||||
**Decided by:** human
|
||||
|
||||
APPROVE: scoped path and confirmed binding are acceptable
|
||||
|
||||
---
|
||||
|
||||
## Review CCR-2026-0001 corrected whynot-design npm publish token lane
|
||||
|
||||
**Date:** 2026-06-27
|
||||
**Decided by:** human
|
||||
|
||||
APPROVE: We fixed the path using coulomb as the org/tenant.
|
||||
|
||||
---
|
||||
|
||||
## Forgejo hostname and exposure model
|
||||
|
||||
**Date:** 2026-07-02
|
||||
**Decided by:** human
|
||||
|
||||
the hostname shall be forgejo.coulomb.social. The exposure model is private repos by default. We will use the transition from gitea to forgejo for closing down public access and establishing credential handling to have convenience when working with the repos. Gitea can and should remain reachable during transition.
|
||||
|
||||
---
|
||||
|
||||
## Forgejo SMTP and sender identity
|
||||
|
||||
**Date:** 2026-07-02
|
||||
**Decided by:** human
|
||||
|
||||
We will use forgejo@coulomb.social
|
||||
|
||||
---
|
||||
|
||||
## Forgejo package registry scope
|
||||
|
||||
**Date:** 2026-07-02
|
||||
**Decided by:** human
|
||||
|
||||
We should support the full range from the start.
|
||||
|
||||
---
|
||||
|
||||
## Forgejo Actions runner isolation model
|
||||
|
||||
**Date:** 2026-07-02
|
||||
**Decided by:** human
|
||||
|
||||
we will try to go with isolated host runners, with least-privilege credential boundaries.
|
||||
|
||||
---
|
||||
|
||||
## Forgejo backup target and restore cadence
|
||||
|
||||
**Date:** 2026-07-02
|
||||
**Decided by:** human
|
||||
|
||||
We will need to figure out the details, but i want to use backup.coulomb.social as the hostname with a backend we need to figure out yet.
|
||||
|
||||
---
|
||||
|
||||
## Forgejo cutover and rollback strategy
|
||||
|
||||
**Date:** 2026-07-02
|
||||
**Decided by:** human
|
||||
|
||||
We will do a staged migration and lock the gitea repos of transitioned repos but keep gitea until everything has been transfered and just then start a 14day trial phase and after that retire gitea to the backup.
|
||||
|
||||
---
|
||||
21
Makefile
Normal file
21
Makefile
Normal file
@@ -0,0 +1,21 @@
|
||||
PYTHON ?= python3
|
||||
REGISTRY_DB ?= .railiance-fabric/registry.sqlite3
|
||||
HOST ?= 127.0.0.1
|
||||
PORT ?= 8765
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: help graph-explorer registry
|
||||
|
||||
help:
|
||||
@printf "Available targets:\n"
|
||||
@printf " make graph-explorer Start the registry-backed graph explorer on HOST:PORT.\n"
|
||||
@printf " Defaults: HOST=$(HOST), PORT=$(PORT), REGISTRY_DB=$(REGISTRY_DB)\n"
|
||||
@printf " make registry Alias for graph-explorer.\n"
|
||||
|
||||
graph-explorer:
|
||||
@mkdir -p $(dir $(REGISTRY_DB))
|
||||
@echo "Starting Railiance Fabric graph explorer at http://$(HOST):$(PORT)/ui/graph-explorer"
|
||||
$(PYTHON) -m railiance_fabric.server --db "$(REGISTRY_DB)" --host "$(HOST)" --port "$(PORT)"
|
||||
|
||||
registry: graph-explorer
|
||||
66
README.md
66
README.md
@@ -1,11 +1,20 @@
|
||||
# Railiance Fabric
|
||||
|
||||
Railiance Fabric defines the repo-owned declaration model for the Railiance
|
||||
ecosystem graph.
|
||||
Railiance Fabric models the durable infrastructure-responsibility graph of the
|
||||
Railiance netkingdom.
|
||||
|
||||
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.
|
||||
Fabric is bounded by financial and operational accountability: who pays for the
|
||||
infrastructure, who is accountable for keeping it alive, and which durable
|
||||
interfaces create value across fabric and subfabric boundaries.
|
||||
|
||||
It holds schemas, discovery tools, registry services, graph queries, and State
|
||||
Hub export contracts for services, machines, repositories, deployables,
|
||||
endpoints, ownership, dependencies, and bindings across Railiance deployment
|
||||
realities.
|
||||
|
||||
See `docs/FabricDiscoveryAndUpdate.md` for the current architecture direction
|
||||
for fabric boundaries, king/lord/tenant ownership, discovery, rebuilds, and
|
||||
update loops.
|
||||
|
||||
## Validate Declarations
|
||||
|
||||
@@ -36,14 +45,35 @@ railiance-fabric blast-radius openbao-kv-v2-mount
|
||||
railiance-fabric export --format json
|
||||
railiance-fabric export --format mermaid
|
||||
railiance-fabric export --format graph-explorer
|
||||
railiance-fabric export --format financial
|
||||
```
|
||||
|
||||
See `docs/discovery-queries.md` for command details.
|
||||
|
||||
## Financial Fabric Baseline
|
||||
|
||||
The current financial Fabric model starts from
|
||||
`fabric/financial/railiance-netkingdom.yaml`. It defines the Railiance
|
||||
netkingdom, the king, the current lord, the one active Railiance fabric, and
|
||||
the default ownership rules used while Railiance still has one effective payer.
|
||||
|
||||
`railiance-fabric export --format financial` projects the current accepted
|
||||
legacy graph into the `railiance.fabric/v1alpha2` / `financial-fabric-v1`
|
||||
contract. Repo-local `fabric/` declarations remain useful evidence and
|
||||
bootstrap data; they are not the long-term authority for external deployment,
|
||||
ownership, tenant, or utility relations.
|
||||
|
||||
See `docs/financial-fabric-operator-guide.md` for the reset/rebuild refresh
|
||||
loop and State Hub handoff.
|
||||
|
||||
## Adopt In Another Repo
|
||||
|
||||
See `docs/adoption-guide.md` for the declaration workflow and
|
||||
`docs/first-rollout.md` for the initial Railiance repo rollout.
|
||||
See `docs/adoption-guide.md` for the legacy declaration workflow and
|
||||
`docs/first-rollout.md` for the initial Railiance repo rollout. Treat
|
||||
repo-local declarations as self-description and discovery evidence. Financial
|
||||
fabric membership, ownership, and cross-boundary utility relations are owned by
|
||||
the accountability-root discovery model described in
|
||||
`docs/FabricDiscoveryAndUpdate.md`.
|
||||
|
||||
## Next: Ecosystem Registry Service
|
||||
|
||||
@@ -51,7 +81,22 @@ See `docs/ecosystem-registry-service.md` for the standards comparison and
|
||||
service direction for registering repos and interacting with the combined
|
||||
ecosystem model. See `docs/registry-api.md` for the current registry HTTP API.
|
||||
|
||||
Start the first registry service slice with:
|
||||
List available Make targets from the repo root:
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
Start the first registry service slice and graph explorer explicitly:
|
||||
|
||||
```bash
|
||||
make graph-explorer
|
||||
```
|
||||
|
||||
The target serves `http://127.0.0.1:8765/ui/graph-explorer` by default. Override
|
||||
`PORT`, `HOST`, or `REGISTRY_DB` if the local port or database path differs.
|
||||
|
||||
Equivalent raw command:
|
||||
|
||||
```bash
|
||||
railiance-fabric-registry --db .railiance-fabric/registry.sqlite3 --port 8765
|
||||
@@ -106,5 +151,6 @@ loop.
|
||||
The graph explorer export is the first executable slice of the interactive
|
||||
Fabric map. See `docs/graph-explorer-transfer-review.md` for the repo-scoping
|
||||
transfer review, `docs/graph-explorer-contract.md` for the shared manifest and
|
||||
payload contract, and `docs/graph-explorer-operations.md` for launch, refresh,
|
||||
verification, and extraction guidance.
|
||||
payload contract, `docs/semantic-attractors.md` for the attractor-based layout
|
||||
orientation concept, and `docs/graph-explorer-operations.md` for launch,
|
||||
refresh, verification, and extraction guidance.
|
||||
|
||||
@@ -87,6 +87,80 @@ spec:
|
||||
- sts-token
|
||||
tags: [storage, credentials, security]
|
||||
|
||||
- id: kubernetes-runtime
|
||||
name: Kubernetes runtime
|
||||
lifecycle: active
|
||||
description: Provides the Kubernetes API, namespaces, workloads, Services, Ingresses, and runtime primitives consumed by Railiance services.
|
||||
default_criticality: critical
|
||||
default_data_classification: restricted
|
||||
expected_interface_types:
|
||||
- kubernetes-api
|
||||
- kubernetes-crd
|
||||
tags: [kubernetes, cluster, runtime]
|
||||
|
||||
- id: ci-cd-template-catalog
|
||||
name: CI/CD template catalog
|
||||
lifecycle: planned
|
||||
description: Provides reusable workflow templates, release gates, and delivery conventions for Railiance workloads.
|
||||
default_criticality: medium
|
||||
default_data_classification: internal
|
||||
expected_interface_types:
|
||||
- workflow-template-contract
|
||||
- cli
|
||||
tags: [ci, cd, gitops, enablement]
|
||||
|
||||
- id: source-hosting
|
||||
name: Source hosting
|
||||
lifecycle: active
|
||||
description: Hosts Git repositories, repository metadata, review surfaces, and source-forge web/API access.
|
||||
default_criticality: high
|
||||
default_data_classification: confidential
|
||||
expected_interface_types:
|
||||
- web-ui
|
||||
- http-api
|
||||
- git-ssh
|
||||
tags: [forge, git, source]
|
||||
|
||||
- id: container-registry
|
||||
name: Container registry
|
||||
lifecycle: active
|
||||
description: Publishes and serves OCI container images for Railiance workloads.
|
||||
default_criticality: high
|
||||
default_data_classification: confidential
|
||||
expected_interface_types:
|
||||
- oci-registry
|
||||
tags: [forge, registry, container-image]
|
||||
|
||||
- id: python-package-registry
|
||||
name: Python package registry
|
||||
lifecycle: active
|
||||
description: Publishes and serves Python package artifacts for Railiance source and app builds.
|
||||
default_criticality: high
|
||||
default_data_classification: confidential
|
||||
expected_interface_types:
|
||||
- python-package-index
|
||||
tags: [forge, registry, python, package]
|
||||
|
||||
- id: workflow-runner-substrate
|
||||
name: Workflow runner substrate
|
||||
lifecycle: planned
|
||||
description: Provides forge-backed runner infrastructure, labels, placement, and credential boundaries for workflows.
|
||||
default_criticality: high
|
||||
default_data_classification: restricted
|
||||
expected_interface_types:
|
||||
- workflow-runner-label-contract
|
||||
tags: [forge, runner, actions, automation]
|
||||
|
||||
- id: artifact-promotion-evidence
|
||||
name: Artifact promotion evidence
|
||||
lifecycle: active
|
||||
description: Provides release artifact identity, provenance, publish, restore, and readiness evidence for consumers.
|
||||
default_criticality: high
|
||||
default_data_classification: internal
|
||||
expected_interface_types:
|
||||
- evidence-contract
|
||||
tags: [forge, evidence, provenance, release]
|
||||
|
||||
- id: audit-event-sink
|
||||
name: Audit/event sink
|
||||
lifecycle: planned
|
||||
@@ -117,6 +191,7 @@ spec:
|
||||
default_data_classification: internal
|
||||
expected_interface_types:
|
||||
- http-api
|
||||
- mcp-api
|
||||
- event-stream
|
||||
tags: [coordination, state-hub, planning]
|
||||
|
||||
|
||||
@@ -25,6 +25,14 @@ spec:
|
||||
typical_auth_methods: [none, oidc, jwt, mtls, api_key]
|
||||
versioning: URL path, static asset version, and documented user-facing workflow compatibility.
|
||||
|
||||
- id: mcp-api
|
||||
name: MCP API
|
||||
lifecycle: active
|
||||
description: Model Context Protocol interface consumed by agents or agent runtimes.
|
||||
category: api
|
||||
typical_auth_methods: [none, oidc, jwt, mtls, api_key]
|
||||
versioning: MCP protocol version, tool schema version, and documented capability compatibility.
|
||||
|
||||
- id: oidc-discovery
|
||||
name: OIDC discovery
|
||||
lifecycle: active
|
||||
@@ -49,6 +57,14 @@ spec:
|
||||
typical_auth_methods: [kubernetes_service_account]
|
||||
versioning: group, version, and kind.
|
||||
|
||||
- id: kubernetes-api
|
||||
name: Kubernetes API
|
||||
lifecycle: active
|
||||
description: Kubernetes API server surface consumed by operators, controllers, and automation.
|
||||
category: kubernetes
|
||||
typical_auth_methods: [kubernetes_service_account, oidc, static_secret]
|
||||
versioning: Kubernetes version, API groups, RBAC contract, and kubeconfig delivery path.
|
||||
|
||||
- id: helm-release
|
||||
name: Helm release
|
||||
lifecycle: active
|
||||
@@ -73,6 +89,54 @@ spec:
|
||||
typical_auth_methods: [database_role, static_secret, openbao_token]
|
||||
versioning: engine version, connection contract, and migration compatibility.
|
||||
|
||||
- id: git-ssh
|
||||
name: Git SSH
|
||||
lifecycle: active
|
||||
description: Git-over-SSH repository access endpoint.
|
||||
category: source-control
|
||||
typical_auth_methods: [static_secret, unknown]
|
||||
versioning: hostname, port, SSH host key, authorized key scope, and Git server compatibility.
|
||||
|
||||
- id: oci-registry
|
||||
name: OCI registry
|
||||
lifecycle: active
|
||||
description: OCI distribution-compatible container image registry endpoint.
|
||||
category: registry
|
||||
typical_auth_methods: [api_key, static_secret, none]
|
||||
versioning: registry host, API behavior, package visibility, and tag/digest semantics.
|
||||
|
||||
- id: python-package-index
|
||||
name: Python package index
|
||||
lifecycle: active
|
||||
description: Python package index endpoint compatible with pip/uv simple API consumption.
|
||||
category: registry
|
||||
typical_auth_methods: [api_key, static_secret, none]
|
||||
versioning: package index URL, package visibility, token scope, and package version semantics.
|
||||
|
||||
- id: workflow-runner-label-contract
|
||||
name: Workflow runner label contract
|
||||
lifecycle: planned
|
||||
description: Published runner label, placement, and trust contract consumed by CI/CD workflows.
|
||||
category: automation
|
||||
typical_auth_methods: [none, kubernetes_service_account, static_secret]
|
||||
versioning: semantic label names, trust level, credential purpose, and runner replacement rules.
|
||||
|
||||
- id: workflow-template-contract
|
||||
name: Workflow template contract
|
||||
lifecycle: planned
|
||||
description: Reusable CI/CD workflow template or template catalog contract.
|
||||
category: automation
|
||||
typical_auth_methods: [none]
|
||||
versioning: template id, input schema, runner labels, and release gate semantics.
|
||||
|
||||
- id: evidence-contract
|
||||
name: Evidence contract
|
||||
lifecycle: active
|
||||
description: Documented evidence bundle or machine-readable evidence contract for release, restore, or readiness decisions.
|
||||
category: evidence
|
||||
typical_auth_methods: [none, api_key]
|
||||
versioning: evidence schema version, required fields, source links, and retention policy.
|
||||
|
||||
- id: object-storage-bucket
|
||||
name: Object-storage bucket
|
||||
lifecycle: planned
|
||||
|
||||
367
docs/FabricDiscoveryAndUpdate.md
Normal file
367
docs/FabricDiscoveryAndUpdate.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# Fabric Discovery And Update
|
||||
|
||||
## Intent
|
||||
|
||||
`railiance-fabric` models the durable infrastructure-responsibility graph of the
|
||||
Railiance netkingdom.
|
||||
|
||||
A fabric is not a repository, environment, cluster, deployment scenario, or
|
||||
security zone. A fabric is the financial and operational responsibility boundary
|
||||
around infrastructure: who pays for it, who is accountable for it, and who has
|
||||
the authority to keep it alive.
|
||||
|
||||
The graph exists to answer:
|
||||
|
||||
- what infrastructure, services, repositories, deployables, endpoints, and
|
||||
machines exist;
|
||||
- who owns or pays for each node;
|
||||
- which fabrics and subfabrics contain those nodes;
|
||||
- which durable interfaces cross ownership boundaries;
|
||||
- which cross-boundary interfaces provide utility that may carry business value.
|
||||
|
||||
Fabric intentionally stays out of live operational telemetry. It may represent
|
||||
that a service, host, endpoint, or deployment exists because durable automation
|
||||
or configuration proves it. It should not represent whether that thing is
|
||||
currently healthy, loaded, failing, or profitable in the accounting sense.
|
||||
|
||||
## Financial Boundary Terms
|
||||
|
||||
Fabric uses financial responsibility terms for fabric boundaries.
|
||||
|
||||
### King
|
||||
|
||||
The king is responsible for the whole netkingdom.
|
||||
|
||||
The king has the key to the netkingdom. Literally, this means the master key or
|
||||
at least a relevant key component for secrets, backups, and recovery paths across
|
||||
the infrastructure under the netkingdom.
|
||||
|
||||
The king also holds the legal and contractual relationship with all lords and
|
||||
tenants. Even when responsibility is delegated, the king remains the actor that
|
||||
can ultimately restore, recover, govern, or terminate the netkingdom.
|
||||
|
||||
### Lord
|
||||
|
||||
A lord pays for a fabric.
|
||||
|
||||
A lord is accountable for a durable infrastructure-responsibility boundary. In
|
||||
the current Railiance setup there is one effective fabric because one actor pays
|
||||
for all infrastructure. Additional fabrics appear when the responsibility to pay
|
||||
for a coherent infrastructure boundary changes.
|
||||
|
||||
### Tenant
|
||||
|
||||
A tenant pays for restricted use of a subfabric.
|
||||
|
||||
A tenant is not the root payer for the whole fabric. A tenant receives bounded
|
||||
access to utility inside a subfabric and may consume services or interfaces
|
||||
provided by a lord, by the king, or by another tenant-facing utility.
|
||||
|
||||
## Fabrics, Subfabrics, And Views
|
||||
|
||||
### Fabric
|
||||
|
||||
A fabric is a durable responsibility boundary. Membership changes when financial
|
||||
or operational responsibility changes, not when a service is redeployed, a repo
|
||||
is refactored, a host is replaced, or an environment name changes.
|
||||
|
||||
Criteria for establishing a fabric:
|
||||
|
||||
- there is a lord who pays for the infrastructure in that boundary;
|
||||
- the boundary can be discovered from durable infrastructure, deployment, or
|
||||
contractual evidence;
|
||||
- the boundary remains stable across ordinary deployment and development churn;
|
||||
- ownership changes imply a meaningful change to who is accountable for cost,
|
||||
continuity, recovery, and utility delivery.
|
||||
|
||||
### Subfabric
|
||||
|
||||
A subfabric is a delegated responsibility or tenancy partition within a fabric.
|
||||
|
||||
Subfabrics are useful when a tenant receives restricted access to infrastructure
|
||||
or utility while the parent fabric remains under a lord or king. A subfabric may
|
||||
eventually become a separate fabric if payment, control, recovery, and legal
|
||||
responsibility move to another lord.
|
||||
|
||||
### View
|
||||
|
||||
A view is a diagnostic or informational slice through the graph.
|
||||
|
||||
Views can filter by repository, service, environment, machine, endpoint, tenant,
|
||||
workstream, deployment stack, or business capability. Views do not define fabric
|
||||
membership and should not mutate fabric boundaries.
|
||||
|
||||
Zone views are a special kind of view. They group the subgraph by deployment
|
||||
environment, deployment scenario, routing authority, or access zone. They are
|
||||
useful for questions such as:
|
||||
|
||||
- which services are currently visible in private dev, shared test, or
|
||||
production;
|
||||
- which control surfaces appear in user-facing zones;
|
||||
- which production surfaces still have unrestricted developer access;
|
||||
- which routes exist in production but not in test;
|
||||
- which early-access interfaces lack an explicit policy authority.
|
||||
|
||||
### Environment
|
||||
|
||||
An environment such as local, dev, staging, production, or lab is a deployment
|
||||
classification inside a fabric. It is not a fabric by itself.
|
||||
|
||||
Railiance uses these baseline environment terms:
|
||||
|
||||
| Environment | Meaning |
|
||||
|-------------|---------|
|
||||
| `dev` | Private developer-local execution. Useful for debugging and discovery, but not shared truth for other developers unless explicitly published. |
|
||||
| `test` | Shared central test stage for collaborators and friendly early-access users. |
|
||||
| `prod` | Production execution. Alpha-stage developer access may exist now, but production must be able to move toward restricted operator access and applicable security and data-privacy controls. |
|
||||
|
||||
### Deployment Scenario
|
||||
|
||||
A deployment scenario is a realized or planned arrangement of services,
|
||||
machines, endpoints, automation, and configuration. It belongs inside a fabric
|
||||
or subfabric but does not define the financial boundary.
|
||||
|
||||
Current Railiance scenario names:
|
||||
|
||||
| Scenario | Environment | Meaning |
|
||||
|----------|-------------|---------|
|
||||
| `bernd-laptop` | `dev` | Private local workstation scenario. It may discover useful loopback services and ports, but it is not a shared developer environment. |
|
||||
| `coulombcore` | `test` | Central shared test-stage server for developers and friendly early-access users. |
|
||||
| `railiance01` | `prod` | Production host. It is still alpha-accessible to developers, but the intended direction is restricted production access. |
|
||||
|
||||
### Routing Authority
|
||||
|
||||
A routing authority is the deployment component that maps an external name,
|
||||
local URL, ingress host, or port to a backend service. Fabric discovers routing
|
||||
authority output; it does not allocate ports or author routes.
|
||||
|
||||
Examples:
|
||||
|
||||
- local loopback process launch configuration for developer tools;
|
||||
- Makefile or script defaults for local-only bootstrap surfaces;
|
||||
- Docker Compose published ports;
|
||||
- Kubernetes `Service`, `Ingress`, or controller-specific route manifests;
|
||||
- Traefik, nginx, Caddy, HAProxy, or other reverse-proxy configuration;
|
||||
- DNS records and TLS/certificate automation when they prove reachable names.
|
||||
|
||||
### Access Zone
|
||||
|
||||
An access zone is a visualization and discovery overlay that describes who a
|
||||
surface is intended to be reachable by in a deployment scenario. It is not a
|
||||
fabric boundary and does not define the policy itself.
|
||||
|
||||
Useful initial access zones:
|
||||
|
||||
| Access zone | Intended reach |
|
||||
|-------------|----------------|
|
||||
| `private-dev` | One developer's local machine. |
|
||||
| `collaborator-test` | Collaborating developers in the shared test stage. |
|
||||
| `early-access` | Friendly users participating in early access. |
|
||||
| `production-public` | Production user-facing surfaces. |
|
||||
| `production-admin` | Production administrative or control surfaces. |
|
||||
|
||||
### Policy Authority
|
||||
|
||||
A policy authority is the system expected to enforce access rules for a surface.
|
||||
Fabric may record which authority is in play, such as NetKingdom IAM, an ingress
|
||||
policy, network policy, or local-only loopback binding, but Fabric does not
|
||||
define or enforce those rules.
|
||||
|
||||
These terms let the graph say: "this service belongs to the Railiance primary
|
||||
fabric, runs in the `coulombcore` test scenario, is routed by Traefik, and is
|
||||
intended for the `early-access` zone." That is a deployment and access overlay,
|
||||
not a change to fabric membership.
|
||||
|
||||
## Ownership Rule
|
||||
|
||||
Every node in the Fabric graph must carry owner information.
|
||||
|
||||
Ownership may be explicit on the node or inherited from the containing fabric or
|
||||
subfabric, but it must be resolvable. Nodes without resolvable ownership are
|
||||
incomplete because Fabric cannot explain who is accountable for their cost,
|
||||
continuity, recovery, or utility.
|
||||
|
||||
Ownership should identify the relevant king, lord, tenant, or delegated operator
|
||||
where applicable. Operators and maintainers may be represented as supporting
|
||||
actors, but they do not replace the financial boundary terms unless they also
|
||||
pay for or legally own the responsibility boundary.
|
||||
|
||||
## Cross-Boundary Edges
|
||||
|
||||
Edges may cross fabric and subfabric boundaries.
|
||||
|
||||
These edges are especially important. They are where one owned boundary consumes
|
||||
utility from another owned boundary. They are also where business value can
|
||||
appear: paid access, cost recovery, shared platform utility, tenant-facing
|
||||
interfaces, or contractual service delivery.
|
||||
|
||||
Fabric should treat cross-boundary interfaces as first-class graph facts. A
|
||||
cross-boundary utility edge should be able to identify:
|
||||
|
||||
- provider owner;
|
||||
- consumer owner;
|
||||
- provider fabric or subfabric;
|
||||
- consumer fabric or subfabric;
|
||||
- exposed endpoint, contract, service, or utility;
|
||||
- durable evidence for the relationship;
|
||||
- expected payment or business model when known;
|
||||
- metering basis when known.
|
||||
|
||||
The existence of a cross-boundary interface belongs in Fabric. Live usage,
|
||||
current health, latency, revenue events, and incident state belong to telemetry,
|
||||
accounting, or operational systems outside the Fabric core.
|
||||
|
||||
## Cost And Profit Center Attribution
|
||||
|
||||
Cost centers and profit centers are accounting attributions. They are not fabric
|
||||
boundaries by themselves.
|
||||
|
||||
A cost center may be evidence that a responsibility boundary exists, especially
|
||||
when it corresponds to a lord who pays for infrastructure. However, the fabric
|
||||
boundary is still established by financial and operational accountability, not
|
||||
by the accounting label alone.
|
||||
|
||||
Cost and profit center attribution may overlap with fabrics and subfabrics:
|
||||
|
||||
- one fabric may contain many cost centers;
|
||||
- one tenant may span several cost centers;
|
||||
- one cost center may include nodes from multiple subfabrics;
|
||||
- one shared service may allocate cost across several tenants or cost centers;
|
||||
- one cross-boundary utility may be cost-bearing for one actor and
|
||||
profit-bearing for another.
|
||||
|
||||
Fabric should therefore support optional accounting attribution on nodes and
|
||||
edges without allowing that attribution to redefine containment:
|
||||
|
||||
- node cost center;
|
||||
- node profit center;
|
||||
- edge cost allocation model;
|
||||
- edge profit attribution;
|
||||
- payment schema;
|
||||
- metering or allocation basis when known;
|
||||
- attribution validity period when known.
|
||||
|
||||
This lets Fabric build cost-center and profit-center views while preserving the
|
||||
durability of fabric membership. A node moving from one cost center to another
|
||||
does not necessarily move fabrics. A node moves fabrics only when financial or
|
||||
operational responsibility for the infrastructure boundary changes.
|
||||
|
||||
Useful accounting views include:
|
||||
|
||||
- all infrastructure attributed to a cost center;
|
||||
- all cross-boundary utility edges attributed to a profit center;
|
||||
- tenant-facing utilities without a payment schema;
|
||||
- shared services with unresolved allocation;
|
||||
- owned nodes without cost-center attribution;
|
||||
- expensive or critical infrastructure with no visible value interface.
|
||||
|
||||
## Discovery Approach
|
||||
|
||||
Fabric discovery should start from accountability roots, not from arbitrary repo
|
||||
ownership declarations.
|
||||
|
||||
Useful roots include:
|
||||
|
||||
- the king and current netkingdom inventory;
|
||||
- lords and their fabrics;
|
||||
- tenants and their subfabrics;
|
||||
- State Hub attached repositories and known host paths;
|
||||
- Gitea organizations and repository URLs;
|
||||
- deployment automation repositories;
|
||||
- Docker, Compose, Kubernetes, systemd, reverse proxy, and infrastructure
|
||||
manifests;
|
||||
- backup, recovery, and secret-management evidence;
|
||||
- service endpoint and API specifications;
|
||||
- CI/CD and release automation that proves deployables and deployment targets.
|
||||
|
||||
The scanner follows these roots outward to discover durable evidence of nodes
|
||||
and edges. Repository-local facts can still help identify deployables, APIs, and
|
||||
artifacts, but repository-owned declarations should not be the default source of
|
||||
truth for external fabric relations.
|
||||
|
||||
## Rebuild From Scratch
|
||||
|
||||
A full rebuild should be deterministic enough to repeat and provenance-rich
|
||||
enough to review.
|
||||
|
||||
Recommended phases:
|
||||
|
||||
1. Establish the netkingdom root and current king.
|
||||
2. Register known lords, fabrics, tenants, and subfabrics.
|
||||
3. Register durable discovery roots for each fabric and subfabric.
|
||||
4. Crawl deployment automation, infrastructure manifests, repo inventories,
|
||||
service configs, and endpoint contracts.
|
||||
5. Store raw evidence with source paths, URLs, timestamps, scanner versions, and
|
||||
content hashes.
|
||||
6. Normalize identities across host paths, repository URLs, image names, service
|
||||
names, endpoint names, and deployment names.
|
||||
7. Build candidate graph nodes and edges.
|
||||
8. Resolve or flag ownership for every node.
|
||||
9. Promote accepted candidates into a versioned fabric snapshot.
|
||||
10. Export the accepted snapshot to State Hub as a read model.
|
||||
|
||||
## Keeping The Model Up To Date
|
||||
|
||||
Fabric freshness should be based on durable configuration and automation changes,
|
||||
with scheduled rescans as a safety net.
|
||||
|
||||
Rescan triggers include:
|
||||
|
||||
- deployment automation changes;
|
||||
- infrastructure manifest changes;
|
||||
- State Hub attached repository inventory changes;
|
||||
- repository changes that affect deployables, APIs, images, service names,
|
||||
endpoint contracts, ports, or deployment configuration;
|
||||
- tenant or lord changes;
|
||||
- backup, recovery, or secret-root changes;
|
||||
- manual operator requests;
|
||||
- scheduled periodic rebuilds.
|
||||
|
||||
Normal source-code edits do not necessarily change the Fabric graph. Fabric
|
||||
should focus on changes that alter durable topology, ownership, deployment,
|
||||
interfaces, or cross-boundary utility.
|
||||
|
||||
Each update should compare new discovery output with the previous accepted
|
||||
snapshot and produce a delta. The delta should distinguish:
|
||||
|
||||
- added, changed, and removed nodes;
|
||||
- added, changed, and removed edges;
|
||||
- changed ownership;
|
||||
- changed fabric or subfabric membership;
|
||||
- new or removed cross-boundary utility interfaces;
|
||||
- ambiguous identity merges that need review;
|
||||
- discovered nodes without resolvable ownership.
|
||||
|
||||
## Boundary With Security Terms
|
||||
|
||||
Fabric should avoid using security-zone language for its core concepts.
|
||||
|
||||
Terms such as realm, domain, tenant realm, identity realm, and security domain
|
||||
are useful in systems such as Microsoft environments or Keycloak, but they carry
|
||||
security and identity-management semantics. Fabric may later relate to those
|
||||
systems as discovered evidence, but the core Fabric concept is financial and
|
||||
operational responsibility, not identity or access-control topology.
|
||||
|
||||
For now, use:
|
||||
|
||||
- king, lord, and tenant for financial responsibility actors;
|
||||
- fabric and subfabric for durable responsibility boundaries;
|
||||
- view for diagnostic graph slices;
|
||||
- environment for deployment classification;
|
||||
- deployment scenario for a concrete place where services run;
|
||||
- routing authority for the component that maps names or ports to services;
|
||||
- access zone for the visualization overlay that groups intended reachability;
|
||||
- policy authority for the external system expected to enforce access rules.
|
||||
|
||||
Access zones may be security-relevant, but they are not Fabric security policy.
|
||||
They are discovered and visualized evidence that helps operators spot misplaced
|
||||
surfaces, missing policy authorities, or accidental exposure.
|
||||
|
||||
## State Hub Role
|
||||
|
||||
State Hub coordinates work and stores a readable projection of the Fabric graph.
|
||||
It should not be the primary author of Fabric topology.
|
||||
|
||||
`railiance-fabric` discovers, normalizes, versions, and exports Fabric snapshots.
|
||||
State Hub imports those snapshots so agents and operators can ask practical
|
||||
questions about work, ownership, dependencies, and cross-boundary utility.
|
||||
337
docs/ZoneEntityVisualization.md
Normal file
337
docs/ZoneEntityVisualization.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# Zone Entity Visualization
|
||||
|
||||
## Intent
|
||||
|
||||
The graph explorer currently treats zones as visual overlays derived from node
|
||||
metadata. That is useful for first orientation, but it keeps zones as a
|
||||
decoration around an already-laid-out graph. The next step is to make a zone a
|
||||
first-class visualization entity beside nodes and edges.
|
||||
|
||||
A zone is a bounded drawing surface for a subgraph. It is similar to a sheet of
|
||||
paper or a layer in a graphics application: it decides which graph elements are
|
||||
drawn inside it, how those elements are laid out, how it stacks with other
|
||||
zones, and whether it can be collapsed into a representative node.
|
||||
|
||||
Zones are view entities. They help the operator inspect the fabric without
|
||||
changing the underlying fabric graph. A zone can be based on deployment
|
||||
environment, ownership, tenant boundaries, cost center, access zone, or any
|
||||
other declared view rule, but those concepts remain distinct from the zone
|
||||
mechanism itself.
|
||||
|
||||
## Core Model
|
||||
|
||||
A graph view contains three visible entity classes:
|
||||
|
||||
- nodes: fabric entities such as services, hosts, repos, scenarios, or owners
|
||||
- edges: fabric relationships such as deployment, routing, ownership, policy,
|
||||
dependency, or evidence links
|
||||
- zones: visual drawing surfaces that contain a selected subgraph
|
||||
|
||||
A zone definition describes how to derive and render one zone. A zone instance
|
||||
is the result of applying a definition to the current graph, filter state, and
|
||||
view profile.
|
||||
|
||||
Zone definitions should be serializable so they can later live in saved graph
|
||||
profiles, operator presets, or a dedicated view registry.
|
||||
|
||||
## Zone Definition
|
||||
|
||||
A zone definition should contain at least these fields:
|
||||
|
||||
```yaml
|
||||
id: prod
|
||||
label: Production
|
||||
enabled: true
|
||||
membership:
|
||||
mode: include
|
||||
rules:
|
||||
- field: deploymentEnvironment
|
||||
op: equals
|
||||
value: prod
|
||||
attraction:
|
||||
rules:
|
||||
- edge_type: fabric:serves
|
||||
direction: both
|
||||
depth: 1
|
||||
node_filter:
|
||||
field: kind
|
||||
op: in
|
||||
value: [service, endpoint]
|
||||
layout:
|
||||
algorithm: cose
|
||||
options: {}
|
||||
presentation:
|
||||
height: 30
|
||||
color: "#0f766e"
|
||||
opacity: 0.16
|
||||
blur_below: true
|
||||
collapse:
|
||||
enabled: true
|
||||
label: Production Zone
|
||||
```
|
||||
|
||||
The exact schema can evolve, but the responsibilities should stay separate:
|
||||
|
||||
- membership chooses seed nodes
|
||||
- attraction optionally expands the zone from those seeds through selected edge
|
||||
types and depths
|
||||
- layout chooses how the zone subgraph is arranged
|
||||
- presentation chooses how the zone itself is drawn and stacked
|
||||
- collapse chooses how the zone can become a representative node
|
||||
|
||||
## Membership Rules
|
||||
|
||||
Membership rules decide which nodes initially belong to a zone. They should be
|
||||
declarative and inspect node data rather than hard-code UI concepts.
|
||||
|
||||
Useful operators:
|
||||
|
||||
- `equals`
|
||||
- `not_equals`
|
||||
- `in`
|
||||
- `not_in`
|
||||
- `exists`
|
||||
- `missing`
|
||||
- `matches`
|
||||
|
||||
Useful rule composition:
|
||||
|
||||
- `all`: every nested rule must match
|
||||
- `any`: at least one nested rule must match
|
||||
- `none`: no nested rule may match
|
||||
|
||||
The first implementation can support a small subset such as `equals`, `in`,
|
||||
`exists`, `all`, and `any`, then widen the rule language once saved zone
|
||||
profiles need it.
|
||||
|
||||
## Attraction Rules
|
||||
|
||||
Attraction rules allow a zone to pull in additional nodes connected to the seed
|
||||
set. This is useful when the operator wants a zone to include the services,
|
||||
routes, machines, or contracts that make the zone understandable, even when
|
||||
those nodes do not carry the seed attribute themselves.
|
||||
|
||||
An attraction rule should define:
|
||||
|
||||
- edge type or edge-type pattern
|
||||
- direction: `out`, `in`, or `both`
|
||||
- maximum depth
|
||||
- optional node filter
|
||||
- optional edge filter
|
||||
|
||||
Attraction must be deterministic. Given the same graph and zone definitions, the
|
||||
same node should land in the same zone every time.
|
||||
|
||||
Attraction also needs a conflict policy because a node must not belong to more
|
||||
than one zone. The recommended first policy is priority by zone height and then
|
||||
definition order:
|
||||
|
||||
1. Seed membership wins over attracted membership.
|
||||
2. If two zones seed the same node, the conflict is reported and the node is
|
||||
assigned to the higher zone.
|
||||
3. If two zones attract the same node, the node is assigned to the higher zone.
|
||||
4. If height ties, the earlier definition wins.
|
||||
5. Every conflict is recorded as a view diagnostic.
|
||||
|
||||
This keeps the single-zone invariant while making ambiguous rules visible.
|
||||
|
||||
## Single-Zone Invariant
|
||||
|
||||
A visible node must be assigned to zero or one zone in a given graph view. A node
|
||||
that belongs to no zone remains on the base canvas.
|
||||
|
||||
The invariant is important because zones are meant to behave like drawing
|
||||
surfaces, not like arbitrary tags. Allowing the same node to appear in multiple
|
||||
zones would make edge routing, selection, collapse behavior, and interaction
|
||||
state ambiguous.
|
||||
|
||||
The model can still support analytical overlap by reporting diagnostics and by
|
||||
offering alternative view presets. It should not solve overlap by duplicating
|
||||
nodes in the same rendered view.
|
||||
|
||||
## Zone Layout
|
||||
|
||||
Each zone may choose its own layout algorithm for its subgraph. For example:
|
||||
|
||||
- deployment zones may use force-directed layout
|
||||
- ownership zones may use concentric layout
|
||||
- stage zones may use grid layout
|
||||
- collapsed or nearly empty zones may use compact deterministic placement
|
||||
|
||||
The outer graph layout and the zone subgraph layouts are separate concerns. A
|
||||
first implementation can keep the current Cytoscape layout for all nodes and
|
||||
only render zone surfaces around assigned nodes. A later implementation should
|
||||
introduce a two-phase layout:
|
||||
|
||||
1. Place zones and unzoned nodes on the main canvas.
|
||||
2. Place each zone's assigned subgraph inside that zone using the zone's layout.
|
||||
|
||||
This will likely require an internal view model that separates fabric graph data
|
||||
from rendered graph coordinates.
|
||||
|
||||
### Per-Zone Layout Preparation
|
||||
|
||||
The current graph explorer should not immediately run independent Cytoscape
|
||||
layouts inside each zone rectangle. Cytoscape layouts operate on collections in
|
||||
one coordinate space, while the current zone overlay is a view layer drawn over
|
||||
the already-laid-out graph. Running nested layouts directly against visible
|
||||
nodes would make pan/zoom, filters, collapse state, and edge routing fragile.
|
||||
|
||||
The safer path is a two-phase view layout:
|
||||
|
||||
1. Resolve zones from the current graph and view filters.
|
||||
2. Place zone containers and unzoned nodes in the global canvas.
|
||||
3. For each zone, compute local coordinates for its assigned nodes using the
|
||||
zone's configured layout algorithm.
|
||||
4. Project local zone coordinates into global graph coordinates.
|
||||
5. Route internal edges inside the zone and boundary edges through the zone
|
||||
perimeter or collapsed zone node.
|
||||
|
||||
The implementation needs these pieces before per-zone layouts become safe:
|
||||
|
||||
- a resolved zone view model that survives filtering and saved profiles;
|
||||
- a stable assignment invariant so a visible node belongs to no more than one
|
||||
zone;
|
||||
- a zone container model with size, position, padding, and height;
|
||||
- a local coordinate projection layer from zone space to Cytoscape space;
|
||||
- explicit boundary-edge routing rules;
|
||||
- collapse state that can replace a zone subgraph with a representative node;
|
||||
- diagnostics when a configured zone layout cannot be applied.
|
||||
|
||||
The present implementation already establishes the first, second, and sixth
|
||||
pieces. A follow-up should introduce a zone container placement phase before
|
||||
attempting per-zone node layout. That follow-up can keep Cytoscape as the final
|
||||
renderer while moving layout decisions into a Fabric-owned view model.
|
||||
|
||||
### Stable Zone Containers
|
||||
|
||||
The first container implementation keeps zone surfaces as view state keyed by
|
||||
stable zone id. When a zone first appears, the global graph layout supplies its
|
||||
initial center. Once created, the container owns the zone surface position while
|
||||
the global layout continues to arrange the base canvas and unzoned nodes.
|
||||
|
||||
Dragging a zone moves the container and its currently assigned visible member
|
||||
nodes together. Rerunning layout or switching the layout algorithm should keep
|
||||
the container in its stored graph coordinates and then project the zone's
|
||||
visible subgraph back into that container.
|
||||
|
||||
Container state belongs in saved or copied graph view state, not in the Fabric
|
||||
payload. It is an operator workspace preference, similar to manual visibility
|
||||
overrides.
|
||||
|
||||
Zone-local layout is also view state. The first selectable algorithms are a
|
||||
compact grid and a circle layout. Switching between them should rearrange the
|
||||
assigned nodes inside each stable container without moving the container itself
|
||||
or changing the underlying Fabric relationships.
|
||||
|
||||
### Context Edges
|
||||
|
||||
Display-only context edges are not zone connectivity. Repository `declares`
|
||||
edges, for example, show which repository declared a node, but they should not
|
||||
create boundary diagnostics, attraction paths, or collapsed-zone boundary
|
||||
edges. A host can still show them as explanatory evidence in details, but a
|
||||
zone boundary should only react to canonical or host-promoted graph
|
||||
relationships.
|
||||
|
||||
## Layer Height And Overlap
|
||||
|
||||
Zone presentation includes a height. Height is a visual stacking concept, not a
|
||||
security or ownership concept.
|
||||
|
||||
When zones overlap:
|
||||
|
||||
- higher zones should draw above lower zones
|
||||
- lower zones may be desaturated when covered
|
||||
- lower zones may be blurred or dimmed when covered
|
||||
- labels should remain readable where practical
|
||||
- pointer interaction should prefer the highest visible zone
|
||||
|
||||
The implementation should avoid implying that a higher zone is more important.
|
||||
Height is about rendering order and view ergonomics only.
|
||||
|
||||
## Collapse To Zone Node
|
||||
|
||||
A zone can optionally collapse into a representative node. Collapsing is a view
|
||||
operation that preserves graph connectivity:
|
||||
|
||||
- nodes inside the zone become hidden from the current view
|
||||
- the zone node represents the collapsed zone
|
||||
- edges between inside and outside nodes are reconnected to the zone node
|
||||
- edges fully inside the zone are hidden or summarized
|
||||
- expanding restores the original nodes and edges
|
||||
|
||||
The collapsed zone node should expose useful summary data:
|
||||
|
||||
- contained node count
|
||||
- contained edge count
|
||||
- dominant node kinds
|
||||
- inbound and outbound boundary edge counts
|
||||
- diagnostics such as membership conflicts
|
||||
|
||||
This enables drill-down exploration without destroying the operator's context.
|
||||
|
||||
## Hierarchical Zones
|
||||
|
||||
The collapse model naturally leads to hierarchical zones. A tenant subfabric may
|
||||
sit inside a landlord fabric, an access zone may sit inside a deployment
|
||||
environment, and a cost-center view may cut across both as an alternate profile.
|
||||
|
||||
Hierarchy should be introduced carefully:
|
||||
|
||||
- parent-child containment must be explicit in the zone definition or derived
|
||||
from a deterministic rule
|
||||
- a node still belongs to exactly one visible leaf zone
|
||||
- collapsing a parent collapses all visible descendants
|
||||
- cross-boundary edges must remain visible at the collapsed boundary
|
||||
|
||||
The first implementation should support flat zones. The data model should avoid
|
||||
choices that would make hierarchy impossible later.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Zones should produce diagnostics because rule-based view construction can hide
|
||||
important ambiguity.
|
||||
|
||||
Useful diagnostics:
|
||||
|
||||
- node matched by more than one membership rule
|
||||
- node attracted by more than one zone
|
||||
- edge crosses zone boundaries
|
||||
- zone has no seed nodes
|
||||
- attraction depth reached limit
|
||||
- collapsed zone has hidden internal boundary evidence
|
||||
|
||||
Diagnostics belong to the visualization layer. They should not mutate the
|
||||
fabric graph.
|
||||
|
||||
## Relationship To Fabric Concepts
|
||||
|
||||
Zones are not fabrics, subfabrics, realms, domains, environments, cost centers,
|
||||
or tenants. A zone is a view mechanism that can visualize those concepts.
|
||||
|
||||
Examples:
|
||||
|
||||
- a deployment-environment zone groups nodes by `dev-tegwick`, `test`, or `prod`
|
||||
- a financial fabric zone groups nodes by lord or tenant responsibility
|
||||
- a cost-center zone groups nodes for accounting analysis
|
||||
- an access-zone view groups nodes by operational access policy once that
|
||||
metadata exists
|
||||
|
||||
This keeps the graph explorer flexible while preserving the stricter meaning of
|
||||
fabric boundaries: who pays for, owns, and is responsible for infrastructure.
|
||||
|
||||
## Implementation Direction
|
||||
|
||||
The recommended path is incremental:
|
||||
|
||||
1. Introduce a pure zone resolver that converts graph data and zone definitions
|
||||
into zone instances, node assignments, edge summaries, and diagnostics.
|
||||
2. Replace the current hard-coded environment/access overlay derivation with
|
||||
resolver-backed default zone definitions.
|
||||
3. Add zone-aware rendering state to the graph explorer UI.
|
||||
4. Add saved zone profiles once the model is stable.
|
||||
5. Add collapse-to-zone-node behavior.
|
||||
6. Add per-zone layout only after the resolver and rendering model are stable.
|
||||
|
||||
This order keeps the current useful overlay behavior while moving the engine
|
||||
toward zones as first-class view entities.
|
||||
172
docs/accountability-root-manifest.md
Normal file
172
docs/accountability-root-manifest.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Accountability Root Manifest
|
||||
|
||||
The accountability root manifest is the handoff between the financial Fabric
|
||||
model and the discovery/update loop.
|
||||
|
||||
It answers where discovery starts. A manifest names the netkingdom, actors,
|
||||
fabric boundaries, and durable roots that can prove repositories, deployment
|
||||
realities, service configuration, endpoint contracts, backup/recovery evidence,
|
||||
and secret-root metadata. It does not collect live telemetry and it does not
|
||||
make State Hub the authoring surface for topology.
|
||||
|
||||
Schema:
|
||||
|
||||
```text
|
||||
schemas/accountability-root-manifest.schema.yaml
|
||||
```
|
||||
|
||||
Current Railiance manifest:
|
||||
|
||||
```text
|
||||
fabric/discovery/railiance-accountability-roots.yaml
|
||||
```
|
||||
|
||||
Tenant/subfabric example:
|
||||
|
||||
```text
|
||||
examples/discovery/accountability-root-manifest.yaml
|
||||
```
|
||||
|
||||
Raw evidence run schema:
|
||||
|
||||
```text
|
||||
schemas/accountability-root-evidence.schema.yaml
|
||||
```
|
||||
|
||||
Identity projection schema:
|
||||
|
||||
```text
|
||||
schemas/accountability-identity-projection.schema.yaml
|
||||
```
|
||||
|
||||
Ownership review schema:
|
||||
|
||||
```text
|
||||
schemas/accountability-ownership-review.schema.yaml
|
||||
```
|
||||
|
||||
Update delta schema:
|
||||
|
||||
```text
|
||||
schemas/accountability-update-delta.schema.yaml
|
||||
```
|
||||
|
||||
## Required Sections
|
||||
|
||||
- `netkingdom`: root id, name, and king actor.
|
||||
- `actors`: king, lord, tenant, operator, or steward actors.
|
||||
- `fabrics`: fabric and subfabric boundaries.
|
||||
- `discovery_roots`: durable roots such as State Hub repo inventory, Gitea
|
||||
organizations, registry manifests, host paths, repo checkouts, deployment
|
||||
automation, endpoint contracts, backup/recovery evidence, and secret-root
|
||||
metadata.
|
||||
- `refresh`: cadence and trigger hints for the future update loop.
|
||||
|
||||
## Boundary Rules
|
||||
|
||||
The current Railiance manifest has one active fabric:
|
||||
`fabric.railiance.primary`. Future tenant subfabrics are added under that
|
||||
fabric by adding a tenant actor, a `Subfabric`, and subfabric-scoped discovery
|
||||
roots. This does not change the root fabric criterion: the fabric boundary
|
||||
still rests on financial and operational accountability.
|
||||
|
||||
`owner_actor_id` on a discovery root describes the default owner to attach to
|
||||
identity candidates discovered through that root. For ordinary repositories,
|
||||
deployment files, and host-path evidence this should be the lord who pays for
|
||||
the fabric. King authority remains modeled on the netkingdom and on roots that
|
||||
represent recovery, secret, backup, or termination authority.
|
||||
|
||||
Discovery roots should state `safe_discovery` explicitly. Secret and backup
|
||||
roots should use `metadata_only` or `explicit_review`; adapters must never read
|
||||
secret values or operational telemetry while building Fabric graph evidence.
|
||||
|
||||
## Collecting Root Evidence
|
||||
|
||||
The first adapter slice emits raw evidence without promoting it into accepted
|
||||
graph snapshots:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots \
|
||||
--manifest fabric/discovery/railiance-accountability-roots.yaml \
|
||||
--max-items-per-root 200
|
||||
```
|
||||
|
||||
The command covers manifest-backed repository inventory, repository checkout
|
||||
identity, host-path evidence, deployment automation and infrastructure files,
|
||||
State Hub/Gitea metadata roots, endpoint/service-config roots, and safe
|
||||
metadata-only backup or secret roots. Remote HTTP reads are disabled by default;
|
||||
pass `--include-remote` only when the operator intentionally wants configured
|
||||
remote roots such as State Hub inventory endpoints to be fetched.
|
||||
|
||||
The output is an `AccountabilityRootEvidenceRun`. Every evidence item carries
|
||||
provenance, source, fingerprint, `durable: true`, and
|
||||
`live_telemetry: false`, preserving the boundary between Fabric evidence and
|
||||
operational telemetry.
|
||||
|
||||
To normalize raw evidence into reviewable identity candidates:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots \
|
||||
--identity-projection \
|
||||
--max-items-per-root 200
|
||||
```
|
||||
|
||||
To persist raw evidence and identity candidates in a local SQLite store:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots \
|
||||
--store-db .railiance-fabric/accountability-evidence.sqlite3 \
|
||||
--identity-projection
|
||||
```
|
||||
|
||||
The store is intentionally separate from accepted registry graph snapshots. It
|
||||
keeps raw evidence runs, evidence items, and identity candidates available for
|
||||
inspection before any candidate is promoted.
|
||||
|
||||
## Ownership Review
|
||||
|
||||
To resolve ownership and containment from the normalized identities:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots \
|
||||
--ownership-review \
|
||||
--store-db .railiance-fabric/accountability-evidence.sqlite3
|
||||
```
|
||||
|
||||
The ownership review inherits owners from fabric/subfabric containment when
|
||||
possible, applies explicit owner evidence from discovery roots, and marks
|
||||
unresolved or ambiguous candidates as `needs_review`. Accepted candidates must
|
||||
have a resolved owner and containment unless they are actors or the netkingdom
|
||||
root.
|
||||
|
||||
To persist a reviewer decision for a stable identity candidate:
|
||||
|
||||
```bash
|
||||
railiance-fabric review-identity identity:repository:example-repo \
|
||||
--store-db .railiance-fabric/accountability-evidence.sqlite3 \
|
||||
--decision accept \
|
||||
--owner-actor-id actor.railiance.primary-lord \
|
||||
--fabric-id fabric.railiance.primary \
|
||||
--reviewer operator \
|
||||
--note "accepted from reviewed checkout evidence"
|
||||
```
|
||||
|
||||
Reviewer decisions are keyed by stable identity key. Later rescans apply the
|
||||
latest decision for that key, so ordinary evidence refreshes do not lose
|
||||
reviewed ownership choices.
|
||||
|
||||
## Update Deltas
|
||||
|
||||
To compare the current run with previous identity and ownership-review outputs:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots \
|
||||
--delta \
|
||||
--previous-identity-projection previous-identities.json \
|
||||
--previous-ownership-review previous-ownership.json
|
||||
```
|
||||
|
||||
The delta separates candidate graph node changes, candidate graph edge changes,
|
||||
ownership changes, containment changes, review-state changes, and blocker
|
||||
changes. When `summary.promotion_needed` is `false`, the update loop can skip
|
||||
promotion because the durable evidence produced no meaningful Fabric change.
|
||||
140
docs/canon-alignment-review.md
Normal file
140
docs/canon-alignment-review.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Canon Alignment Review For RAIL-FAB-WP-0016
|
||||
|
||||
Date: 2026-05-23
|
||||
|
||||
## Review Packet
|
||||
|
||||
This review follows the InfoTechCanon consumer alignment workflow for
|
||||
`railiance-fabric`.
|
||||
|
||||
Reviewed Fabric sources:
|
||||
|
||||
- `INTENT.md`, `SCOPE.md`, and `README.md`
|
||||
- `railiance_fabric/graph.py`, `scanner.py`, `registry.py`, and `graph_explorer.py`
|
||||
- `schemas/discovery-snapshot.schema.yaml` and `schemas/state-hub-export.schema.yaml`
|
||||
- workplans `RAIL-FAB-WP-0010` through `RAIL-FAB-WP-0016`
|
||||
|
||||
Reviewed canon sources:
|
||||
|
||||
- `infospace/agent/review-kit/review-kit.yaml`
|
||||
- `infospace/agent/review-kit/review-workflow.yaml`
|
||||
- `infospace/evaluations/railiance-fabric/conformance-pack.yaml`
|
||||
- `infospace/evaluations/railiance-fabric/entity-edge-capture-criteria.yaml`
|
||||
- `infospace/evaluations/railiance-fabric/mapping-expectations.yaml`
|
||||
- `infospace/evaluations/railiance-fabric/visualization-examples.yaml`
|
||||
|
||||
The workplan's `review-kit/alignment` reference is the canon artifact id; the
|
||||
physical files are under `infospace/agent/review-kit/`.
|
||||
|
||||
## Repository Context
|
||||
|
||||
Producer intent: Railiance Fabric owns repo-authored graph declarations,
|
||||
scanner output, registry projection, validation, query tooling, and State Hub
|
||||
export contracts for the Railiance ecosystem graph.
|
||||
|
||||
Current scope: source-controlled declarations model services, capabilities,
|
||||
interfaces, dependencies, and binding assertions. Deterministic scanning adds
|
||||
candidate repositories, libraries, manifests, runtime endpoints, domains,
|
||||
ports, and source evidence. The graph explorer adds visual projection edges and
|
||||
inferred runtime views for usability.
|
||||
|
||||
Consumer purposes:
|
||||
|
||||
- make cross-repo dependencies reviewable,
|
||||
- make provider/consumer and blast-radius queries reliable,
|
||||
- provide State Hub with a read model rather than an authoring surface,
|
||||
- support reingest after a canon-aligned model reset,
|
||||
- prevent visualization metadata from becoming graph truth.
|
||||
|
||||
## Selected Canon Surfaces
|
||||
|
||||
| Surface | Why selected |
|
||||
| --- | --- |
|
||||
| `model/landscape` | Services, software systems, runtime resources, environments, ownership, and dependency claims. |
|
||||
| `model/devsecops` | Source repositories, packages, lockfiles, pipelines, artifacts, deployments, and attestations. |
|
||||
| `model/network` | Endpoints, ports, DNS, routes, reachability, and future flow nodes. |
|
||||
| `model/data` | Datastore and reads/writes relationships are expected gaps in current Fabric capture. |
|
||||
| `model/observability` | Evidence, telemetry signals, scanner provenance, and validation support. |
|
||||
| `model/governance` | Policies, reviews, decisions, exceptions, and reset guardrails. |
|
||||
| `model/security` | Controls and findings that should not be flattened into generic dependency edges. |
|
||||
| `model/task` | Review and remediation work created from graph gaps. |
|
||||
| `model/purpose-demand-extension` | Consumer purpose, scope pressure, and canon evolution feedback. |
|
||||
| `standard/tagging` | Non-relationship classification without using display edges as semantics. |
|
||||
|
||||
## Target Node Taxonomy
|
||||
|
||||
| Canon category | Current Fabric source | Fit | Target action |
|
||||
| --- | --- | --- | --- |
|
||||
| source-repository | `Repository` candidates and registered repos | direct | Keep as source evidence for repo-local declarations and scans. |
|
||||
| service | `ServiceDeclaration` | direct | Keep as logical/deployable service boundary. |
|
||||
| software-system | `CapabilityDeclaration`, `Library`, `ExternalLibrary` | partial | Keep as transitional mapping; later split capability semantics from system/component boundaries. |
|
||||
| endpoint | `InterfaceDeclaration`, `ApplicationEndpoint`, `NetworkPort`, `DomainName` | partial/direct | Prefer explicit endpoint nodes for network reachability; preserve interface contract attributes. |
|
||||
| deployment | `DeploymentService`, `ScoreWorkload`, `ContainerBuild` | partial/direct | Add deployment nodes for actual release/deploy evidence before destructive reset. |
|
||||
| runtime-resource | `RuntimeService`, `Server`, `Kubernetes*` candidates | partial/direct | Capture workload, service DNS, namespace, VM/server, and cluster objects as runtime reality. |
|
||||
| datastore | none first-class | gap | Add explicit datastore extraction from declarations, manifests, and config before reingest acceptance. |
|
||||
| flow | route/resolve/port edges only | gap | Add first-class Flow nodes when observed traffic or declared communication needs protocol/evidence context. |
|
||||
| policy | none first-class | gap | Add policy nodes for governing declarations, reset gates, and validation controls. |
|
||||
| control | none first-class | gap | Add control nodes only when preventive/detective/corrective controls have evidence. |
|
||||
| evidence | `BindingAssertion`, `Lockfile`, `ServiceConfig`, contracts, source anchors | partial | Make evidence explicit instead of hiding it only in attributes. |
|
||||
| task | workplans and review gaps | gap | Capture review/remediation tasks after State Hub readiness work is agreed. |
|
||||
| consumer-purpose | interface card and purpose-fit review | gap | Keep in docs now; model as graph nodes only if State Hub needs purpose-driven filtering. |
|
||||
| telemetry-signal | none first-class | gap | Add metrics/logs/alerts/dashboards as observed signals when connectors exist. |
|
||||
|
||||
## Target Edge Taxonomy
|
||||
|
||||
| Current Fabric edge | Canon relationship | Fit | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `exposes`, `exposes_port`, `listens_on` | `exposes` | direct/partial | Good first canonical family for service/endpoint reachability. |
|
||||
| `consumes`, `depends_on_library`, `binds:*`, `uses_interface` | `depends_on` | partial | Preserve helper nodes until a reingest can project direct service relationships safely. |
|
||||
| `provides` | `implements` | partial | Service-to-capability is a Fabric helper relation; needs stronger canon treatment or collapse. |
|
||||
| `available_via`, `names_endpoint` | `exposes` | partial | Useful transitional mappings from capability/interface/domain to endpoint. |
|
||||
| `defines_deployment`, `builds_container`, `declares_package` | `built_from` | partial | Direction and artifact semantics need cleanup before being canonical claims. |
|
||||
| `defines_runtime_object`, `defines_workload`, `runs_on`, `deployed_as` | `deploys` | partial | Current scanner/UI edges mix source definitions and deployment reality. |
|
||||
| `routes_to_port`, `routes_to_service`, `resolves_to` | `flows_to` | partial | These are reachability hints, not logical dependencies. |
|
||||
| `uses_lockfile`, `uses_config`, `documents_interface`, `cataloged_as` | `evidenced_by` | partial | Evidence should become first-class where it supports a specific claim. |
|
||||
| `declares`, `owns_deployment`, canon display examples | display-only | direct | These are view/cluster edges and must not be conformance claims. |
|
||||
|
||||
## Mapping Findings
|
||||
|
||||
Direct mappings are strong for repositories, services, runtime resources,
|
||||
application endpoints, network ports, and explicit `exposes` relationships.
|
||||
|
||||
Partial mappings are expected for capability, interface, dependency, binding,
|
||||
package, manifest, and route concepts. They should keep legacy names during the
|
||||
transition but carry `canon_category`, `canonical_type`, `mapping_fit`, and
|
||||
`evidence_state` metadata.
|
||||
|
||||
Conflicts: network flow must not be treated as logical dependency, and
|
||||
graph-explorer layout edges must not be treated as canonical ownership,
|
||||
dependency, reachability, policy, or evidence.
|
||||
|
||||
Gaps: datastores, flows, policy, control, telemetry signals, tasks, and
|
||||
consumer-purpose nodes are not first-class scanner outputs yet. These should be
|
||||
implemented as explicit gaps or candidate mappings, not forced into nearby
|
||||
legacy Fabric semantics.
|
||||
|
||||
## Execution Direction
|
||||
|
||||
The first implementation slice adds canon metadata beside existing node and
|
||||
edge names. That keeps registry and graph explorer behavior stable while making
|
||||
the renewed model inspectable and testable.
|
||||
|
||||
The destructive reset phase remains blocked until the following exist:
|
||||
|
||||
- export and archive command for current registry graph data,
|
||||
- reset command that requires an explicit operator flag,
|
||||
- rollback note that explains restore limits,
|
||||
- validation that the new scanner/projection can reingest registered repos,
|
||||
- before/after counts and sample graph review.
|
||||
|
||||
No destructive reset was executed during this T01/T02 start.
|
||||
|
||||
## Canon Feedback
|
||||
|
||||
- Capability and dependency helper nodes are useful Fabric authoring concepts
|
||||
but do not map cleanly to canonical graph entity categories.
|
||||
- Edge direction needs explicit treatment for source repo to package/deployment
|
||||
evidence, because canon `built_from` points from deployment/artifact context
|
||||
back to source.
|
||||
- Evidence state should remain independent from review state: accepted inferred
|
||||
claims and candidate declared claims are different situations.
|
||||
100
docs/canon-interface-card.yaml
Normal file
100
docs/canon-interface-card.yaml
Normal file
@@ -0,0 +1,100 @@
|
||||
schema: info-tech-canon.interface-card.v1
|
||||
id: railiance-fabric/interface-card
|
||||
title: Railiance Fabric Canon Interface Card
|
||||
consumer:
|
||||
repo: railiance-fabric
|
||||
domain: railiance
|
||||
owner: codex
|
||||
intent: Make the Railiance ecosystem understandable, discoverable, and evolvable through accepted Fabric snapshots, accountability-root discovery, and repo-local evidence.
|
||||
scope: Shared schemas, validation, graph construction, registry projections, scanner output, and State Hub export contracts for repository, service, capability, interface, dependency, binding, and runtime discovery data.
|
||||
purposes:
|
||||
- id: purpose/graph-refactor
|
||||
use_case: Canon-aligned graph model reset and reingest.
|
||||
consumer_need: Separate canonical graph semantics from registry, scanner, and visualization convenience edges before broad adoption.
|
||||
demand_signals:
|
||||
- RAIL-FAB-WP-0016
|
||||
- Registry graph explorer needs clearer canonical/display boundary.
|
||||
- Scanner output needs evidence state and canon mapping metadata.
|
||||
canon_surfaces:
|
||||
implemented_profiles: []
|
||||
consumed_artifacts:
|
||||
- model/landscape
|
||||
- model/network
|
||||
- model/data
|
||||
- model/devsecops
|
||||
- model/observability
|
||||
- model/governance
|
||||
- model/security
|
||||
- model/task
|
||||
- model/purpose-demand-extension
|
||||
- standard/tagging
|
||||
- conformance/railiance-fabric
|
||||
owned_concepts:
|
||||
- FabricGraphExport
|
||||
- FabricDiscoverySnapshot
|
||||
- GraphExplorerPayload
|
||||
- RegistryOnboardingManifest
|
||||
produced_concepts:
|
||||
- FabricEntity
|
||||
- FabricEdge
|
||||
- CaptureSource
|
||||
- DisplayEdge
|
||||
- CanonicalEdgeCandidate
|
||||
- VisualizationView
|
||||
consumed_concepts:
|
||||
- Repository
|
||||
- Service
|
||||
- SoftwareSystem
|
||||
- RuntimeResource
|
||||
- Endpoint
|
||||
- Deployment
|
||||
- Flow
|
||||
- Evidence
|
||||
- Task
|
||||
mappings:
|
||||
- source: Repository
|
||||
target: source-repository
|
||||
fit: direct
|
||||
- source: ServiceDeclaration
|
||||
target: service
|
||||
fit: direct
|
||||
- source: InterfaceDeclaration
|
||||
target: endpoint
|
||||
fit: partial
|
||||
- source: CapabilityDeclaration
|
||||
target: software-system
|
||||
fit: partial
|
||||
- source: DependencyDeclaration
|
||||
target: depends_on edge
|
||||
fit: gap
|
||||
validation_expectations:
|
||||
commands:
|
||||
- python3 -m pytest
|
||||
evidence_required:
|
||||
- Nodes carry canon_category, canon_anchor, mapping_fit, and evidence_state.
|
||||
- Edges carry canonical_type, display_only, mapping_fit, and evidence_state.
|
||||
- Display-only edges do not act as conformance claims.
|
||||
- Reset commands are guarded and documented before graph data is dropped.
|
||||
known_gaps:
|
||||
- Datastore, flow, policy, control, telemetry-signal, task, and consumer-purpose are not first-class scanner outputs yet.
|
||||
- Legacy declaration nodes still preserve capability/dependency/binding helper nodes until reingest rules collapse or replace them safely.
|
||||
purpose_fit:
|
||||
state: partial-fit
|
||||
matched_capabilities:
|
||||
- entity and edge capture criteria
|
||||
- mapping expectations
|
||||
- visualization boundary examples
|
||||
scope_pressure: Fabric needs a stable edge vocabulary and evidence-state vocabulary for mixed declared, observed, inferred, and proposed graph claims.
|
||||
recommended_disposition: Continue consumer-side refactor; record canon gaps as explicit feedback rather than silently extending Fabric semantics.
|
||||
consumer_needs:
|
||||
current:
|
||||
- Canon-aligned graph metadata in scanner, registry, export, and graph explorer payloads.
|
||||
- Safe destructive reset guardrails before dropping previous graph snapshots.
|
||||
- Reingest validation across registered and local repositories.
|
||||
requested_extensions:
|
||||
- Stable relationship vocabulary for graph capture.
|
||||
- Evidence-state vocabulary for captured edges.
|
||||
- Visualization boundary guidance for display-only edges.
|
||||
feedback:
|
||||
- CapabilityDeclaration and DependencyDeclaration are useful Fabric authoring helpers but do not map cleanly to canon node categories.
|
||||
- Network flow and logical dependency must remain separate even when both are inferred from the same source artifact.
|
||||
@@ -1,12 +1,18 @@
|
||||
# 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 declarations are small YAML documents that capture repo-local
|
||||
self-description evidence. The first schema version is
|
||||
`railiance.fabric/v1alpha1`.
|
||||
|
||||
These declarations are still supported for bootstrap and compatibility, but
|
||||
they are not the long-term authority for external deployment realities,
|
||||
financial fabric boundaries, tenant subfabrics, or cross-boundary utility
|
||||
relations. The financial Fabric model starts from accountability roots as
|
||||
described in `docs/FabricDiscoveryAndUpdate.md`.
|
||||
|
||||
## File Layout
|
||||
|
||||
Participating repositories should use this layout:
|
||||
Participating repositories that publish local evidence should use this layout:
|
||||
|
||||
```text
|
||||
fabric/
|
||||
@@ -56,7 +62,7 @@ begin with the owning repo slug when possible:
|
||||
| `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.owner` | Local evidence owner for the declaration; financial Fabric ownership is resolved separately. |
|
||||
| `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. |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Discovery Queries And Exports
|
||||
|
||||
Railiance Fabric includes a first CLI surface for inspecting local declaration
|
||||
graphs.
|
||||
graphs and projecting them into the current financial Fabric baseline.
|
||||
|
||||
All commands accept a repo root, `fabric/` directory, or declaration files. When
|
||||
paths are omitted, commands read `./fabric`.
|
||||
@@ -84,13 +84,30 @@ Export the graph as the manifest-compatible graph explorer payload:
|
||||
railiance-fabric export --format graph-explorer
|
||||
```
|
||||
|
||||
Export the graph as the financial Fabric baseline projection:
|
||||
|
||||
```bash
|
||||
railiance-fabric export --format financial
|
||||
```
|
||||
|
||||
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`
|
||||
|
||||
Canon-aligned exports also carry mapping metadata beside the existing Fabric
|
||||
terms: nodes include `canon_category`, `canon_anchor`, `mapping_fit`, and
|
||||
`evidence_state`; edges include `canonical_type`, `display_only`,
|
||||
`mapping_fit`, and `evidence_state`.
|
||||
|
||||
The graph explorer payload wraps those nodes and edges as Cytoscape-compatible
|
||||
elements with stable keys, layers, display state, visual facets, source
|
||||
references, and deep links. The registry service exposes the same projection at
|
||||
`GET /exports/graph-explorer`.
|
||||
|
||||
The financial export emits `railiance.fabric/v1alpha2` /
|
||||
`financial-fabric-v1`. It combines the current legacy graph evidence with
|
||||
`fabric/financial/railiance-netkingdom.yaml`, assigning inherited ownership and
|
||||
fabric containment for the current single Railiance fabric. Use it as the reset
|
||||
contract and State Hub vNext handoff artifact.
|
||||
|
||||
@@ -7,12 +7,16 @@ 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.
|
||||
model over accepted Fabric graph snapshots, discovery evidence, and supporting
|
||||
inventory.
|
||||
|
||||
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 registry should not become the authoring surface for Fabric ownership or
|
||||
boundaries. Repo-local `fabric/` files remain useful evidence, while financial
|
||||
fabric membership, tenant boundaries, ownership, and cross-boundary utility
|
||||
relations are resolved from accountability roots and accepted snapshots. 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.
|
||||
|
||||
421
docs/financial-fabric-model-audit.md
Normal file
421
docs/financial-fabric-model-audit.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# Financial Fabric Model Audit
|
||||
|
||||
Date: 2026-05-24
|
||||
|
||||
Workplan: `RAIL-FAB-WP-0017`
|
||||
|
||||
## Purpose
|
||||
|
||||
This audit identifies the current `railiance-fabric` assumptions that must
|
||||
change to implement the financial Fabric architecture in
|
||||
`docs/FabricDiscoveryAndUpdate.md`.
|
||||
|
||||
The current implementation is still centered on repo-owned declarations:
|
||||
|
||||
```text
|
||||
repo -> service -> capability -> interface -> dependency -> binding
|
||||
```
|
||||
|
||||
That model remains useful as legacy evidence and as a self-description layer,
|
||||
but it is no longer the correct top-level source of truth for external fabric
|
||||
relations. The vNext model must start from accountability roots: netkingdom,
|
||||
king, lords, tenants, fabrics, subfabrics, deployment automation, durable
|
||||
infrastructure evidence, and cross-boundary utility interfaces.
|
||||
|
||||
## Current Model Assumptions
|
||||
|
||||
### Repo-Owned Declarations Are The Primary Graph Source
|
||||
|
||||
Affected files:
|
||||
|
||||
- `INTENT.md`
|
||||
- `SCOPE.md`
|
||||
- `docs/declaration-schema.md`
|
||||
- `docs/adoption-guide.md`
|
||||
- `docs/first-rollout.md`
|
||||
- `docs/repo-reality-scanner.md`
|
||||
- `fabric/README.md`
|
||||
- `examples/declarations/**`
|
||||
- `fabric/services/**`
|
||||
- `fabric/capabilities/**`
|
||||
- `fabric/interfaces/**`
|
||||
- `fabric/dependencies/**`
|
||||
- `fabric/bindings/**`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- Repositories are expected to own declarations for services, capabilities,
|
||||
interfaces, dependencies, and binding assertions.
|
||||
- The scanner treats repo-owned Fabric declarations as the highest-trust source
|
||||
for accepted graph data.
|
||||
- Existing examples and docs teach adoption by adding `fabric/` files to
|
||||
participating repositories.
|
||||
|
||||
Required change:
|
||||
|
||||
- Repo-owned declarations should become one evidence source, mostly useful for
|
||||
self-description, not the default authority for external relations.
|
||||
- External relations, tenancy, deployment membership, and cross-boundary utility
|
||||
edges should come from fabric-scoped discovery roots and deployment evidence.
|
||||
|
||||
### Declaration Kinds Encode The Graph Shape
|
||||
|
||||
Affected files:
|
||||
|
||||
- `schemas/service.schema.yaml`
|
||||
- `schemas/capability.schema.yaml`
|
||||
- `schemas/interface.schema.yaml`
|
||||
- `schemas/dependency.schema.yaml`
|
||||
- `schemas/binding.schema.yaml`
|
||||
- `schemas/common.schema.yaml`
|
||||
- `railiance_fabric/model.py`
|
||||
- `railiance_fabric/loader.py`
|
||||
- `railiance_fabric/validation.py`
|
||||
- `railiance_fabric/graph.py`
|
||||
- `railiance_fabric/canon.py`
|
||||
- `tests/test_canon.py`
|
||||
- `tests/test_registry.py`
|
||||
- `tests/test_scanner.py`
|
||||
- `tests/test_discovery.py`
|
||||
- `tests/test_discovery_registry.py`
|
||||
- `tests/test_reconciliation.py`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- `loader.py` hardcodes declaration directories:
|
||||
`services`, `capabilities`, `interfaces`, `dependencies`, and `bindings`.
|
||||
- `validation.py` only accepts the legacy declaration kinds through
|
||||
`SCHEMA_BY_KIND`.
|
||||
- `graph.py` builds provider/consumer/binding behavior directly from those
|
||||
declaration kinds.
|
||||
- `canon.py` maps those declaration kinds into canonical categories, with
|
||||
several partial or gap mappings.
|
||||
- Tests assert the legacy kinds and edge types as the normal graph shape.
|
||||
|
||||
Required change:
|
||||
|
||||
- Add vNext semantic graph concepts for netkingdom, actor, fabric, subfabric,
|
||||
owned node, utility interface, accounting attribution, and provenance.
|
||||
- Keep legacy declaration kinds as a compatibility/evidence layer until they
|
||||
can be migrated or intentionally retired.
|
||||
- Stop treating environment tags as possible fabric boundaries.
|
||||
|
||||
### Metadata Owner Is Not Financial Ownership
|
||||
|
||||
Affected files:
|
||||
|
||||
- `schemas/common.schema.yaml`
|
||||
- `railiance_fabric/graph.py`
|
||||
- `railiance_fabric/scanner.py`
|
||||
- `railiance_fabric/registry.py`
|
||||
- `schemas/state-hub-export.schema.yaml`
|
||||
- existing declaration files under `fabric/**`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- `metadata.owner` is required for declarations, but it is just a string.
|
||||
- Graph exports place owner inside node `attributes.owner`, not as a first-class
|
||||
field.
|
||||
- Discovery candidates can carry owner-like attributes, but there is no
|
||||
enforced owner actor, role, inheritance rule, or unresolved-owner state.
|
||||
- State Hub export nodes require `repo`, `domain`, and `lifecycle`, but not
|
||||
financial owner, fabric, or subfabric.
|
||||
|
||||
Required change:
|
||||
|
||||
- Introduce actor identity and role: king, lord, tenant, and supporting
|
||||
operator/steward where needed.
|
||||
- Every accepted node must resolve to an owner actor, either explicit or
|
||||
inherited from containing fabric/subfabric.
|
||||
- Missing or ambiguous ownership must be represented as review state before
|
||||
promotion.
|
||||
|
||||
### Fabric Membership Is Missing
|
||||
|
||||
Affected files:
|
||||
|
||||
- `schemas/state-hub-export.schema.yaml`
|
||||
- `schemas/discovery-snapshot.schema.yaml`
|
||||
- `railiance_fabric/scanner.py`
|
||||
- `railiance_fabric/registry.py`
|
||||
- `railiance_fabric/graph_explorer.py`
|
||||
- `railiance_fabric/graph_explorer_ui.py`
|
||||
- `docs/graph-explorer-contract.md`
|
||||
- `docs/state-hub-integration.md`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- Accepted graph nodes have repo/domain/lifecycle fields, but no `fabric_id`,
|
||||
`subfabric_id`, containment path, or owning lord/tenant relation.
|
||||
- Discovery snapshots are scoped to one `repo_slug`, one commit, and one scan
|
||||
profile.
|
||||
- Registry snapshots are stored per repository and combined by merging the
|
||||
latest snapshot for each repo.
|
||||
|
||||
Required change:
|
||||
|
||||
- Add first-class fabric/subfabric identity and containment.
|
||||
- Preserve repo scoping for repository evidence, but do not let repo scope
|
||||
define fabric membership.
|
||||
- Add a root netkingdom/fabric baseline for the current one-fabric Railiance
|
||||
setup.
|
||||
|
||||
### Cross-Boundary Utility Is Flattened Into Dependency Edges
|
||||
|
||||
Affected files:
|
||||
|
||||
- `schemas/dependency.schema.yaml`
|
||||
- `schemas/binding.schema.yaml`
|
||||
- `railiance_fabric/graph.py`
|
||||
- `railiance_fabric/registry.py`
|
||||
- `railiance_fabric/canon.py`
|
||||
- `railiance_fabric/graph_explorer.py`
|
||||
- `railiance_fabric/graph_explorer_ui.py`
|
||||
- `tests/test_registry.py`
|
||||
- `tests/test_graph_explorer.py`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- Consumers and providers are expressed as dependencies and binding assertions.
|
||||
- Edges such as `consumes`, `binds:*`, and `uses_interface` map mostly to
|
||||
canonical `depends_on`.
|
||||
- There is no explicit provider-owner/consumer-owner boundary, payment schema,
|
||||
metering basis, or business model on the edge.
|
||||
|
||||
Required change:
|
||||
|
||||
- Add a first-class cross-boundary utility edge concept.
|
||||
- Preserve technical dependency edges, but distinguish them from economic or
|
||||
tenancy value interfaces.
|
||||
- Allow cross-boundary edges across fabric and subfabric boundaries without
|
||||
treating the boundary crossing as an error.
|
||||
|
||||
### Cost/Profit Centers Are Not Represented
|
||||
|
||||
Affected files:
|
||||
|
||||
- all current graph schemas and exports
|
||||
- `railiance_fabric/registry.py`
|
||||
- `railiance_fabric/graph_explorer.py`
|
||||
- `railiance_fabric/graph_explorer_ui.py`
|
||||
- State Hub import contract in `schemas/state-hub-export.schema.yaml`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- There are no node or edge fields for cost center, profit center, allocation
|
||||
model, payment schema, metering basis, or attribution validity.
|
||||
|
||||
Required change:
|
||||
|
||||
- Add optional accounting attribution on nodes and edges.
|
||||
- Ensure accounting attribution can drive views without changing fabric or
|
||||
subfabric membership.
|
||||
|
||||
### Discovery Has Good Mechanics But Wrong Roots
|
||||
|
||||
Affected files:
|
||||
|
||||
- `railiance_fabric/scanner.py`
|
||||
- `railiance_fabric/discovery.py`
|
||||
- `railiance_fabric/reconciliation.py`
|
||||
- `railiance_fabric/connectors.py`
|
||||
- `schemas/discovery-snapshot.schema.yaml`
|
||||
- `docs/repo-reality-scanner.md`
|
||||
- `docs/operational-rescan-loops.md`
|
||||
- `docs/registry-onboarding.md`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- Discovery snapshots are provenance-rich and already separate candidates from
|
||||
accepted graph state.
|
||||
- Replacement scopes, stable keys, tombstones, review states, reconciliation
|
||||
diffs, and connector runs are already present.
|
||||
- Discovery is still repo-scoped and gives `repo_declaration` precedence.
|
||||
- `local-fabric-registry` reads repository onboarding manifests, not
|
||||
accountability roots or deployment responsibility boundaries.
|
||||
|
||||
Required change:
|
||||
|
||||
- Reuse the raw-evidence/candidate/accepted mechanics.
|
||||
- Add accountability-root manifests and adapters for deployment automation,
|
||||
infrastructure evidence, recovery/secrets/backup evidence, and ownership
|
||||
boundaries.
|
||||
- Change precedence so repo declarations can be strong self-description
|
||||
evidence without overriding fabric-scoped deployment evidence.
|
||||
|
||||
### State Hub Export Is Too Thin For The New Model
|
||||
|
||||
Affected files:
|
||||
|
||||
- `schemas/state-hub-export.schema.yaml`
|
||||
- `railiance_fabric/graph.py`
|
||||
- `railiance_fabric/registry.py`
|
||||
- `railiance_fabric/server.py`
|
||||
- `docs/state-hub-integration.md`
|
||||
- `docs/registry-api.md`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- `/exports/state-hub` returns a `FabricGraphExport` with nodes and edges.
|
||||
- Nodes require `id`, `kind`, `name`, `repo`, `domain`, and `lifecycle`.
|
||||
- Edges require `from`, `to`, and `type`.
|
||||
- Canon metadata and evidence state are present, but ownership, containment,
|
||||
accounting, and utility metadata are not first-class.
|
||||
|
||||
Required change:
|
||||
|
||||
- Version the export contract.
|
||||
- Add fabric/subfabric containment, owner actor identity, owner role,
|
||||
cross-boundary utility metadata, and optional cost/profit attribution.
|
||||
- Keep enough compatibility metadata for State Hub to support old imports until
|
||||
`STATE-WP-0051` replaces or migrates the read model.
|
||||
|
||||
## Affected APIs And Commands
|
||||
|
||||
HTTP surfaces:
|
||||
|
||||
- `GET /exports/state-hub`
|
||||
- `GET /exports/graph-explorer`
|
||||
- `GET /exports/backstage`
|
||||
- `GET /exports/xregistry`
|
||||
- `GET /graph/providers`
|
||||
- `GET /graph/consumers`
|
||||
- `GET /graph/unresolved`
|
||||
- `GET /graph/blast-radius`
|
||||
- `GET /graph/dependency-path`
|
||||
- `POST /repositories/{repo_slug}/snapshots`
|
||||
- `POST /repositories/{repo_slug}/discovery-snapshots`
|
||||
- `POST /repositories/{repo_slug}/discovery-snapshots/{id}/accept`
|
||||
- `GET /repositories/{repo_slug}/inventory`
|
||||
|
||||
CLI surfaces:
|
||||
|
||||
- `railiance-fabric validate`
|
||||
- `railiance-fabric export`
|
||||
- `railiance-fabric providers`
|
||||
- `railiance-fabric consumers`
|
||||
- `railiance-fabric dependency-path`
|
||||
- `railiance-fabric unresolved`
|
||||
- `railiance-fabric blast-radius`
|
||||
- `railiance-fabric scan`
|
||||
- `railiance-fabric registry sync`
|
||||
- `railiance-fabric registry sync-manifest`
|
||||
- `railiance-fabric registry scan-manifest`
|
||||
- `railiance-fabric registry ingest-discovery`
|
||||
- `railiance-fabric registry accept-discovery`
|
||||
- `railiance-fabric registry rescan-status`
|
||||
|
||||
Projection surfaces:
|
||||
|
||||
- Graph Explorer payload and manifest.
|
||||
- Backstage projection.
|
||||
- xRegistry projection.
|
||||
- State Hub Fabric read-model import.
|
||||
|
||||
## Compatibility Risks
|
||||
|
||||
1. State Hub import compatibility:
|
||||
`STATE-WP-0050` expects the current `FabricGraphExport` shape. Adding
|
||||
required fields or changing node/edge semantics can break ingest unless the
|
||||
export is versioned or State Hub is updated in lockstep.
|
||||
|
||||
2. Graph Explorer compatibility:
|
||||
Graph Explorer filters and detail panes use current node kinds, layers,
|
||||
dependency chains, unresolved states, and canon metadata. New actor/fabric
|
||||
nodes and utility edges need UI mapping before the explorer remains useful.
|
||||
|
||||
3. Query command compatibility:
|
||||
Provider/consumer/dependency-path commands are built on capability and
|
||||
dependency declarations. They may remain useful as legacy views, but should
|
||||
not be presented as the whole Fabric model.
|
||||
|
||||
4. Registry storage compatibility:
|
||||
The registry stores snapshots as JSON, so schema migration is flexible, but
|
||||
combined graph behavior currently deduplicates by node id across latest
|
||||
per-repo snapshots. Fabric-scoped snapshots may need a root snapshot or
|
||||
scenario/fabric identity instead of only repo latest snapshots.
|
||||
|
||||
5. Test fixture blast radius:
|
||||
Most tests assert legacy declaration kinds, repo-scoped discovery keys, and
|
||||
current export payload shape. The migration should add vNext tests before
|
||||
replacing old assertions.
|
||||
|
||||
6. Canon mapping ambiguity:
|
||||
Current canonical categories do not include financial actors, fabrics,
|
||||
subfabrics, accounting attributions, or utility value interfaces. These
|
||||
either need canonical mappings or explicit `mapping_fit: gap` handling.
|
||||
|
||||
7. Existing accepted graph baseline:
|
||||
The current accepted graph contains 49 nodes and 58 edges from the
|
||||
post-`RAIL-FAB-WP-0016` reset. It should be treated as a legacy baseline
|
||||
and re-exported or archived before a controlled vNext reset.
|
||||
|
||||
## Migration Approach
|
||||
|
||||
1. Preserve legacy declarations as `v1alpha1` evidence.
|
||||
Do not delete `fabric/` declarations or examples immediately. Reclassify
|
||||
them as self-description and bootstrap evidence.
|
||||
|
||||
2. Introduce a vNext contract alongside the current export.
|
||||
Add schema version metadata before making breaking changes. Prefer either
|
||||
`railiance.fabric/v1alpha2` or an explicit export `schema_version`.
|
||||
|
||||
3. Add first-class model concepts before changing scanner roots.
|
||||
Implement netkingdom, actor, fabric, subfabric, ownership, utility edge, and
|
||||
accounting attribution semantics in schemas/tests first.
|
||||
|
||||
4. Extend discovery rather than replacing it.
|
||||
Reuse `FabricDiscoverySnapshot`, replacement scopes, candidates,
|
||||
provenance, reconciliation, and acceptance. Add accountability-root
|
||||
manifest evidence and deployment automation adapters.
|
||||
|
||||
5. Seed the current one-fabric baseline.
|
||||
Create the Railiance netkingdom root, current king, current lord/fabric, and
|
||||
inherited ownership defaults. This lets existing nodes resolve ownership
|
||||
before tenant subfabrics exist.
|
||||
|
||||
6. Version State Hub import.
|
||||
Coordinate with `STATE-WP-0051` so State Hub can ingest both the old and
|
||||
vNext export during the transition, or explicitly perform a read-model reset.
|
||||
|
||||
7. Rebuild and compare.
|
||||
Produce a vNext export from the current baseline, compare it against the
|
||||
legacy 49-node/58-edge export, and document intentional semantic changes.
|
||||
|
||||
## Follow-On Implementation Notes
|
||||
|
||||
For `RAIL-FAB-WP-0017-T02`:
|
||||
|
||||
- Define schema names and versioning for vNext graph exports.
|
||||
- Decide whether netkingdom/fabric/actor are node kinds, top-level export
|
||||
sections, or both.
|
||||
- Define ownership inheritance and unresolved-owner representation.
|
||||
- Define utility edge fields:
|
||||
provider owner, consumer owner, provider boundary, consumer boundary,
|
||||
payment schema, metering basis, and evidence.
|
||||
- Define accounting fields:
|
||||
cost center, profit center, allocation model, attribution validity.
|
||||
|
||||
For `RAIL-FAB-WP-0017-T03`:
|
||||
|
||||
- Add model classes or typed helpers beyond the generic `Declaration` wrapper.
|
||||
- Extend validation to enforce resolvable ownership on accepted nodes.
|
||||
- Update registry projection to preserve ownership, containment, accounting,
|
||||
and utility metadata.
|
||||
- Keep old declaration graph validation available as a legacy command or
|
||||
compatibility path.
|
||||
|
||||
For `RAIL-FAB-WP-0017-T04`:
|
||||
|
||||
- Update `schemas/state-hub-export.schema.yaml`.
|
||||
- Add sample vNext export payloads and tests.
|
||||
- Coordinate the import fields with `STATE-WP-0051`.
|
||||
|
||||
For `RAIL-FAB-WP-0018`:
|
||||
|
||||
- Build an accountability-root manifest instead of extending
|
||||
`registry/local-repos.yaml` into too many meanings.
|
||||
- Treat State Hub attached repos as one discovery root, not as the Fabric
|
||||
boundary itself.
|
||||
- Add deployment automation and infrastructure evidence adapters before trying
|
||||
to infer cross-boundary value edges.
|
||||
226
docs/financial-fabric-operator-guide.md
Normal file
226
docs/financial-fabric-operator-guide.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Financial Fabric Operator Guide
|
||||
|
||||
This guide is the operator path for the financial Fabric reset described in
|
||||
`docs/FabricDiscoveryAndUpdate.md`.
|
||||
|
||||
## Current Baseline
|
||||
|
||||
Railiance currently has one effective fabric because one actor pays for the
|
||||
infrastructure. The accepted baseline lives at:
|
||||
|
||||
```text
|
||||
fabric/financial/railiance-netkingdom.yaml
|
||||
```
|
||||
|
||||
It defines:
|
||||
|
||||
- `railiance.netkingdom` as the netkingdom root;
|
||||
- `actor.railiance.king` as the king with recovery, secrets, backup, and
|
||||
termination authority;
|
||||
- `actor.railiance.primary-lord` as the lord who pays for the current fabric;
|
||||
- `fabric.railiance.primary` as the current active fabric;
|
||||
- inherited ownership for accepted nodes until a more specific owner is
|
||||
discovered or approved;
|
||||
- a future subfabric template for tenant modeling.
|
||||
|
||||
The baseline is not a security-zone model. Do not introduce realm, domain, or
|
||||
identity-zone language into the Fabric core when updating it.
|
||||
|
||||
## Deployment Overlay Baseline
|
||||
|
||||
Fabric membership is not the same as deployment environment. Use these current
|
||||
Railiance deployment overlays when interpreting discovery evidence:
|
||||
|
||||
| Scenario | Environment | Access zone baseline | Notes |
|
||||
|----------|-------------|----------------------|-------|
|
||||
| `bernd-laptop` | `dev` | `private-dev` | Local machine used by one developer. Its loopback ports and local tools are useful evidence for debugging, but are not shared truth for other developers. |
|
||||
| `coulombcore` | `test` | `collaborator-test`, `early-access` | Central test-stage server for collaborating developers and friendly early-access users. |
|
||||
| `railiance01` | `prod` | `production-public`, `production-admin` | Production host. Developer access exists during alpha, but production should be able to move toward restricted operator access and applicable security and data-privacy controls. |
|
||||
|
||||
The routing authority is the source that maps names or ports to services in a
|
||||
scenario. For local dev this may be a Makefile, script, process launcher, or
|
||||
loopback binding. For test and production this should usually come from
|
||||
Kubernetes `Service` and `Ingress`, Traefik/nginx/Caddy/HAProxy configuration,
|
||||
DNS, and TLS automation.
|
||||
|
||||
Fabric should discover those routes and visualize the resulting zones. It
|
||||
should not allocate ports, author reverse-proxy config, or define the access
|
||||
policy. The policy authority is external evidence, such as NetKingdom IAM,
|
||||
ingress policy, network policy, or local-only loopback binding.
|
||||
|
||||
## Refresh Loop
|
||||
|
||||
Run these checks from the `railiance-fabric` repo after changing declarations,
|
||||
baseline ownership, discovery inputs, or export code:
|
||||
|
||||
```bash
|
||||
railiance-fabric validate .
|
||||
railiance-fabric export --format json
|
||||
railiance-fabric export --format financial
|
||||
python3 -m pytest tests/test_registry.py -q
|
||||
```
|
||||
|
||||
Use the legacy JSON export for compatibility with existing `STATE-WP-0050`
|
||||
State Hub behavior. Use the financial export to verify the vNext contract and
|
||||
the ownership/fabric projection.
|
||||
|
||||
For accountability-root discovery, start from the current root manifest:
|
||||
|
||||
```text
|
||||
fabric/discovery/railiance-accountability-roots.yaml
|
||||
```
|
||||
|
||||
The manifest schema is documented in `docs/accountability-root-manifest.md`.
|
||||
|
||||
To collect raw evidence from those roots without promoting graph state:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots --max-items-per-root 200
|
||||
```
|
||||
|
||||
To inspect normalized identity candidates or persist a local evidence run:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots --identity-projection
|
||||
railiance-fabric discover-roots --store-db .railiance-fabric/accountability-evidence.sqlite3
|
||||
```
|
||||
|
||||
To inspect ownership blockers and apply review decisions:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots --ownership-review \
|
||||
--store-db .railiance-fabric/accountability-evidence.sqlite3
|
||||
|
||||
railiance-fabric review-identity <stable-key> \
|
||||
--store-db .railiance-fabric/accountability-evidence.sqlite3 \
|
||||
--decision accept \
|
||||
--owner-actor-id actor.railiance.primary-lord \
|
||||
--fabric-id fabric.railiance.primary
|
||||
```
|
||||
|
||||
To compare a new run with saved review outputs:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots --delta \
|
||||
--previous-identity-projection previous-identities.json \
|
||||
--previous-ownership-review previous-ownership.json
|
||||
```
|
||||
|
||||
The current bootstrap artifacts live at:
|
||||
|
||||
```text
|
||||
fabric/discovery/snapshots/2026-05-24-railiance-bootstrap-identities.json
|
||||
fabric/discovery/snapshots/2026-05-24-railiance-bootstrap-ownership-review.json
|
||||
fabric/discovery/snapshots/2026-05-24-railiance-bootstrap-update-delta.json
|
||||
exports/state-hub/2026-05-24-railiance-financial-fabric-v1.json
|
||||
```
|
||||
|
||||
To refresh the same artifact set:
|
||||
|
||||
```bash
|
||||
railiance-fabric discover-roots --include-remote --max-items-per-root 200 \
|
||||
--identity-projection > fabric/discovery/snapshots/YYYY-MM-DD-railiance-bootstrap-identities.json
|
||||
railiance-fabric discover-roots --include-remote --max-items-per-root 200 \
|
||||
--ownership-review > fabric/discovery/snapshots/YYYY-MM-DD-railiance-bootstrap-ownership-review.json
|
||||
railiance-fabric discover-roots --include-remote --max-items-per-root 200 \
|
||||
--delta > fabric/discovery/snapshots/YYYY-MM-DD-railiance-bootstrap-update-delta.json
|
||||
railiance-fabric export --format financial . \
|
||||
> exports/state-hub/YYYY-MM-DD-railiance-financial-fabric-v1.json
|
||||
```
|
||||
|
||||
The financial export must satisfy these invariants:
|
||||
|
||||
- every accepted node has resolvable ownership;
|
||||
- every accepted node has fabric containment;
|
||||
- cost/profit center fields are optional accounting attribution, not fabric
|
||||
boundaries;
|
||||
- cross-boundary utility edges carry provider and consumer owner context when
|
||||
known;
|
||||
- unresolved ownership or containment remains explicit rather than invented by
|
||||
State Hub.
|
||||
|
||||
## Registry Reset Rebuild
|
||||
|
||||
After a guarded registry reset, rebuild from durable roots in this order:
|
||||
|
||||
1. Confirm the baseline still reflects who pays for the infrastructure.
|
||||
2. Validate the repo evidence with `railiance-fabric validate .`.
|
||||
3. Emit `railiance-fabric export --format financial` and review the ownership
|
||||
projection.
|
||||
4. Start the registry and sync this repo:
|
||||
|
||||
```bash
|
||||
railiance-fabric-registry --db .railiance-fabric/registry.sqlite3 --port 8765
|
||||
railiance-fabric registry sync --repo-slug railiance-fabric .
|
||||
```
|
||||
|
||||
5. Sync broader known repos with `registry/local-repos.yaml` or
|
||||
`registry/railiance-repos.yaml` when their local checkouts exist.
|
||||
6. Compare snapshot diffs before promoting new discovery output.
|
||||
|
||||
Repo-local `fabric/` declarations are evidence during this rebuild. The
|
||||
replacement discovery loop should start from the netkingdom, king, lords,
|
||||
tenants, deployment automation, service manifests, endpoints, backups, and
|
||||
secret roots rather than asking each repo to own all external relations.
|
||||
|
||||
## State Hub Handoff
|
||||
|
||||
State Hub is a read model for Fabric topology. It should ingest Fabric exports,
|
||||
link them to repos/workstreams/tasks, and expose dashboard/search views. It
|
||||
should not author ownership, fabric membership, or utility boundaries.
|
||||
|
||||
Until `STATE-WP-0051` is implemented:
|
||||
|
||||
- continue importing or syncing the legacy `v1alpha1` graph for existing State
|
||||
Hub graph views;
|
||||
- keep the `v1alpha2` financial export as the contract artifact for State Hub
|
||||
implementation and review;
|
||||
- update file-backed workplan state through State Hub consistency after
|
||||
workplan edits:
|
||||
|
||||
```bash
|
||||
cd ~/state-hub
|
||||
make fix-consistency REPO=railiance-fabric
|
||||
```
|
||||
|
||||
After `STATE-WP-0051`, the financial export should become the preferred State
|
||||
Hub graph import. The importer must preserve netkingdom, actors, fabrics,
|
||||
containment, ownership, accounting attribution, cross-boundary utility context,
|
||||
and unresolved gaps.
|
||||
|
||||
To import a saved financial export into State Hub:
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
"http://127.0.0.1:8000/fabric/graph-exports?source_repo_slug=railiance-fabric" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @exports/state-hub/2026-05-24-railiance-financial-fabric-v1.json
|
||||
```
|
||||
|
||||
If the `/fabric/graph-exports` endpoints return `500` while ordinary State Hub
|
||||
routes work, run the State Hub migrations and retry:
|
||||
|
||||
```bash
|
||||
cd ~/state-hub
|
||||
make migrate
|
||||
# or, when uv is not on PATH:
|
||||
.venv/bin/alembic upgrade head
|
||||
```
|
||||
|
||||
## Discovery Work Handoff
|
||||
|
||||
The next discovery/update-loop work should replace the baseline projection with
|
||||
durable evidence collection:
|
||||
|
||||
- discover repos from State Hub and Gitea;
|
||||
- discover deployment realities from automation and infrastructure manifests;
|
||||
- discover services, machines, endpoints, backup roots, and secret roots;
|
||||
- normalize identities across repo URLs, host paths, image names, service
|
||||
names, and endpoint contracts;
|
||||
- resolve owner and containment from the baseline, explicit evidence, or
|
||||
review decisions;
|
||||
- emit deltas for ownership, containment, nodes, edges, and cross-boundary
|
||||
utility changes.
|
||||
|
||||
The baseline projection is therefore a safe bridge, not the final discovery
|
||||
engine.
|
||||
493
docs/financial-fabric-vnext-contract.md
Normal file
493
docs/financial-fabric-vnext-contract.md
Normal file
@@ -0,0 +1,493 @@
|
||||
# Financial Fabric VNext Contract
|
||||
|
||||
Date: 2026-05-24
|
||||
|
||||
Workplan: `RAIL-FAB-WP-0017`
|
||||
|
||||
Status: contract draft for implementation
|
||||
|
||||
## Intent
|
||||
|
||||
The vNext Fabric contract models the Railiance netkingdom as a durable
|
||||
infrastructure-responsibility graph.
|
||||
|
||||
The contract separates:
|
||||
|
||||
- financial responsibility boundaries from environments and views;
|
||||
- ownership from repository metadata;
|
||||
- cross-boundary utility interfaces from ordinary technical dependencies;
|
||||
- accounting attribution from fabric membership;
|
||||
- durable topology from live telemetry.
|
||||
|
||||
The contract should be implemented as a versioned export shape before replacing
|
||||
the existing `railiance.fabric/v1alpha1` declaration-centered graph.
|
||||
|
||||
## Versioning
|
||||
|
||||
Recommended contract identity:
|
||||
|
||||
```yaml
|
||||
apiVersion: railiance.fabric/v1alpha2
|
||||
kind: FabricGraphExport
|
||||
schema_version: financial-fabric-v1
|
||||
```
|
||||
|
||||
`v1alpha1` remains the compatibility layer for legacy declarations and the
|
||||
current State Hub import. `v1alpha2` introduces first-class netkingdom,
|
||||
actor, fabric, subfabric, ownership, utility, and accounting semantics.
|
||||
|
||||
## Top-Level Export Shape
|
||||
|
||||
```yaml
|
||||
apiVersion: railiance.fabric/v1alpha2
|
||||
kind: FabricGraphExport
|
||||
schema_version: financial-fabric-v1
|
||||
generated_at: "2026-05-24T00:00:00Z"
|
||||
source:
|
||||
producer: railiance-fabric
|
||||
registry: registry
|
||||
commit: ""
|
||||
generation_reason: rebuild | rescan | operator_refresh
|
||||
compatibility:
|
||||
legacy_v1alpha1_supported: true
|
||||
breaking_reset: false
|
||||
netkingdom:
|
||||
id: railiance.netkingdom
|
||||
name: Railiance Netkingdom
|
||||
king_actor_id: actor.railiance.king
|
||||
actors: []
|
||||
fabrics: []
|
||||
nodes: []
|
||||
edges: []
|
||||
unresolved: []
|
||||
```
|
||||
|
||||
The top-level sections are intentionally explicit. `nodes` and `edges` remain
|
||||
the primary graph payload for State Hub and visualizers, but `netkingdom`,
|
||||
`actors`, and `fabrics` define the responsibility boundary context used to
|
||||
resolve ownership and containment.
|
||||
|
||||
## Actors
|
||||
|
||||
Actors are accountable parties. They are graph addressable and may also be
|
||||
materialized as nodes when a consumer needs a pure node/edge projection.
|
||||
|
||||
```yaml
|
||||
id: actor.railiance.king
|
||||
kind: FabricActor
|
||||
role: king
|
||||
name: Railiance King
|
||||
legal_name: ""
|
||||
description: Responsible for the Railiance netkingdom.
|
||||
authority:
|
||||
recovery_authority: true
|
||||
secrets_authority: true
|
||||
backup_authority: true
|
||||
termination_authority: true
|
||||
relationships:
|
||||
contracts_with:
|
||||
- actor.railiance.primary-lord
|
||||
```
|
||||
|
||||
Actor roles:
|
||||
|
||||
- `king`: responsible for the netkingdom and relevant recovery/secrets/backup
|
||||
authority.
|
||||
- `lord`: pays for a fabric.
|
||||
- `tenant`: pays for restricted use of a subfabric.
|
||||
- `operator`: operates infrastructure on behalf of a king, lord, or tenant.
|
||||
- `steward`: maintains a repo, service, or capability but does not necessarily
|
||||
pay for it.
|
||||
|
||||
Only king, lord, and tenant define financial boundary semantics. Operator and
|
||||
steward roles are supporting roles.
|
||||
|
||||
## Fabrics And Subfabrics
|
||||
|
||||
Fabrics and subfabrics are responsibility boundaries.
|
||||
|
||||
```yaml
|
||||
id: fabric.railiance.primary
|
||||
kind: Fabric
|
||||
name: Railiance Primary Fabric
|
||||
netkingdom_id: railiance.netkingdom
|
||||
lord_actor_id: actor.railiance.primary-lord
|
||||
parent_fabric_id: null
|
||||
boundary:
|
||||
boundary_type: fabric
|
||||
criterion: financial_and_operational_accountability
|
||||
payment_responsibility: actor.railiance.primary-lord
|
||||
operational_responsibility: actor.railiance.king
|
||||
recovery_responsibility: actor.railiance.king
|
||||
status: active
|
||||
evidence_refs: []
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: subfabric.railiance.tenant.coulomb
|
||||
kind: Subfabric
|
||||
name: Coulomb Tenant Subfabric
|
||||
netkingdom_id: railiance.netkingdom
|
||||
parent_fabric_id: fabric.railiance.primary
|
||||
tenant_actor_id: actor.coulomb.tenant
|
||||
boundary:
|
||||
boundary_type: subfabric
|
||||
criterion: restricted_paid_tenant_utility
|
||||
payment_responsibility: actor.coulomb.tenant
|
||||
operational_responsibility: actor.railiance.primary-lord
|
||||
recovery_responsibility: actor.railiance.king
|
||||
status: planned
|
||||
evidence_refs: []
|
||||
```
|
||||
|
||||
Fabric membership changes when payment, operational responsibility, recovery
|
||||
responsibility, or legal accountability changes. It does not change because a
|
||||
service moves environments, a host is replaced, a repo is refactored, or a
|
||||
diagnostic view changes.
|
||||
|
||||
## Nodes
|
||||
|
||||
Every accepted node must resolve ownership and containment.
|
||||
|
||||
```yaml
|
||||
id: state-hub.api
|
||||
kind: Service
|
||||
name: State Hub API
|
||||
repo: state-hub
|
||||
domain: custodian
|
||||
lifecycle: active
|
||||
containment:
|
||||
netkingdom_id: railiance.netkingdom
|
||||
fabric_id: fabric.railiance.primary
|
||||
subfabric_id: null
|
||||
environment: local
|
||||
deployment_scenario_id: null
|
||||
ownership:
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
owner_role: lord
|
||||
resolution: inherited
|
||||
inherited_from: fabric.railiance.primary
|
||||
supporting_actor_ids:
|
||||
- actor.state-hub.steward
|
||||
accounting:
|
||||
cost_center_id: null
|
||||
profit_center_id: null
|
||||
allocation_model: null
|
||||
evidence:
|
||||
state: observed
|
||||
review_state: accepted
|
||||
confidence: 0.95
|
||||
refs: []
|
||||
attributes: {}
|
||||
```
|
||||
|
||||
Required accepted-node semantics:
|
||||
|
||||
- `containment.netkingdom_id`
|
||||
- `containment.fabric_id`
|
||||
- `ownership.owner_actor_id`
|
||||
- `ownership.owner_role`
|
||||
- `ownership.resolution`
|
||||
- `evidence.state`
|
||||
- `evidence.review_state`
|
||||
|
||||
Recommended compatibility fields:
|
||||
|
||||
- `repo`
|
||||
- `domain`
|
||||
- `lifecycle`
|
||||
- `canon_category`
|
||||
- `canon_anchor`
|
||||
- `mapping_fit`
|
||||
- `attributes`
|
||||
|
||||
## Ownership Resolution
|
||||
|
||||
Ownership resolution values:
|
||||
|
||||
- `explicit`: evidence directly names the owner actor.
|
||||
- `inherited`: ownership is inherited from containing fabric or subfabric.
|
||||
- `unresolved`: no acceptable owner could be resolved.
|
||||
- `ambiguous`: multiple plausible owners exist and review is needed.
|
||||
|
||||
Unresolved or ambiguous nodes may appear as candidates or importable read-model
|
||||
gaps, but they must not silently become accepted graph truth.
|
||||
|
||||
```yaml
|
||||
id: unresolved:node:fixture
|
||||
target_id: fixture.unknown-service
|
||||
kind: unresolved_ownership
|
||||
severity: needs_review
|
||||
message: Node has no explicit owner and no resolvable fabric/subfabric owner.
|
||||
evidence_refs: []
|
||||
```
|
||||
|
||||
## Edges
|
||||
|
||||
Edges keep technical topology while adding boundary and value semantics.
|
||||
|
||||
```yaml
|
||||
id: edge:state-hub.api:exposes:state-hub.http
|
||||
from: state-hub.api
|
||||
to: state-hub.http
|
||||
type: exposes
|
||||
relationship_category: technical
|
||||
canonical_type: exposes
|
||||
boundary:
|
||||
crosses_fabric_boundary: false
|
||||
crosses_subfabric_boundary: false
|
||||
evidence:
|
||||
state: observed
|
||||
review_state: accepted
|
||||
confidence: 0.95
|
||||
refs: []
|
||||
attributes: {}
|
||||
```
|
||||
|
||||
Relationship categories:
|
||||
|
||||
- `containment`: netkingdom, fabric, subfabric, actor, or node containment.
|
||||
- `ownership`: owner/steward/operator relationships.
|
||||
- `technical`: deployment, exposure, dependency, flow, build, or evidence edge.
|
||||
- `utility`: value-bearing interface from provider to consumer.
|
||||
- `accounting`: cost/profit attribution or allocation edge.
|
||||
- `evidence`: source artifact or provenance relationship.
|
||||
|
||||
## Cross-Boundary Utility Edges
|
||||
|
||||
Cross-boundary utility edges are first-class because they represent value moving
|
||||
across financial responsibility boundaries.
|
||||
|
||||
```yaml
|
||||
id: utility:state-hub-api:coulomb-tenant
|
||||
from: state-hub.http
|
||||
to: tenant.coulomb.automation-client
|
||||
type: provides_utility_to
|
||||
relationship_category: utility
|
||||
canonical_type: depends_on
|
||||
provider:
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
fabric_id: fabric.railiance.primary
|
||||
subfabric_id: null
|
||||
consumer:
|
||||
owner_actor_id: actor.coulomb.tenant
|
||||
fabric_id: fabric.railiance.primary
|
||||
subfabric_id: subfabric.railiance.tenant.coulomb
|
||||
boundary:
|
||||
crosses_fabric_boundary: false
|
||||
crosses_subfabric_boundary: true
|
||||
utility:
|
||||
utility_type: coordination_api
|
||||
contract_id: state-hub.http
|
||||
payment_schema_id: payment.internal-tenant-access
|
||||
metering_basis: request | seat | flat | allocation | unknown
|
||||
business_model: tenant_utility
|
||||
accounting:
|
||||
provider_profit_center_id: null
|
||||
consumer_cost_center_id: null
|
||||
allocation_model: null
|
||||
evidence:
|
||||
state: declared
|
||||
review_state: accepted
|
||||
confidence: 0.8
|
||||
refs: []
|
||||
attributes: {}
|
||||
```
|
||||
|
||||
The edge may cross a subfabric boundary without crossing the parent fabric
|
||||
boundary. This is expected for tenant utility inside the current Railiance
|
||||
fabric.
|
||||
|
||||
## Cost And Profit Center Attribution
|
||||
|
||||
Accounting attribution is optional and must not redefine containment.
|
||||
|
||||
Node-level attribution:
|
||||
|
||||
```yaml
|
||||
accounting:
|
||||
cost_center_id: cc.platform.shared
|
||||
profit_center_id: null
|
||||
allocation_model: direct
|
||||
valid_from: "2026-05-24"
|
||||
valid_until: null
|
||||
```
|
||||
|
||||
Edge-level attribution:
|
||||
|
||||
```yaml
|
||||
accounting:
|
||||
provider_profit_center_id: pc.tenant-utilities
|
||||
consumer_cost_center_id: cc.coulomb.automation
|
||||
allocation_model: usage_weighted
|
||||
payment_schema_id: payment.internal-tenant-access
|
||||
metering_basis: request
|
||||
valid_from: "2026-05-24"
|
||||
valid_until: null
|
||||
```
|
||||
|
||||
Cost/profit center changes should produce accounting deltas and views, not
|
||||
fabric moves.
|
||||
|
||||
## Evidence And Provenance
|
||||
|
||||
Every discovered or accepted fact should carry evidence.
|
||||
|
||||
```yaml
|
||||
evidence:
|
||||
state: observed | declared | inferred | proposed | gap
|
||||
review_state: accepted | candidate | needs_review | rejected
|
||||
confidence: 0.0
|
||||
refs:
|
||||
- id: evidence:state-hub:compose:api
|
||||
source_kind: deployment_manifest
|
||||
source_path: compose.yaml
|
||||
source_url: null
|
||||
source_repo: state-hub
|
||||
source_commit: abc123
|
||||
scanner: compose
|
||||
scanner_version: "0.1.0"
|
||||
content_hash: sha256:...
|
||||
observed_at: "2026-05-24T00:00:00Z"
|
||||
```
|
||||
|
||||
The contract continues to distinguish durable discovered configuration from
|
||||
live operational telemetry. Health, current running state, latency, incident
|
||||
status, and usage volume are not Fabric core fields.
|
||||
|
||||
## Legacy Canon Mapping
|
||||
|
||||
Existing canonical metadata remains useful for compatibility, but some legacy
|
||||
terms should be demoted to evidence.
|
||||
|
||||
| Existing kind/type | vNext disposition |
|
||||
| --- | --- |
|
||||
| `Repository` | Keep as source-repository node evidence. |
|
||||
| `ServiceDeclaration` | Legacy self-description evidence for `Service`. |
|
||||
| `CapabilityDeclaration` | Legacy self-description evidence for utility or service capability. |
|
||||
| `InterfaceDeclaration` | Legacy self-description evidence for `Endpoint` or `Contract`. |
|
||||
| `DependencyDeclaration` | Legacy consumer-purpose or dependency evidence; not sufficient for value edge alone. |
|
||||
| `BindingAssertion` | Legacy binding evidence; may support utility edge when boundary actors are known. |
|
||||
| `RuntimeService`, `DeploymentService`, `Kubernetes*`, `Server` | Durable deployment/runtime-resource evidence. |
|
||||
| `provides`, `exposes`, `runs_on`, `deployed_as`, `flows_to` | Keep as technical/topology edges. |
|
||||
| `consumes`, `binds:*`, `uses_interface` | Keep as technical dependency edges; promote to utility edges only when provider/consumer ownership and boundary context are known. |
|
||||
| `declares` | Display/evidence edge only. |
|
||||
|
||||
New vNext node kinds to map:
|
||||
|
||||
| vNext kind | Suggested canonical category |
|
||||
| --- | --- |
|
||||
| `Netkingdom` | `software-system` until canon has a better category. |
|
||||
| `FabricActor` | `consumer-purpose` or `control` depending role; likely canon gap. |
|
||||
| `Fabric` | `software-system` or `control`; likely canon gap. |
|
||||
| `Subfabric` | `software-system` or `control`; likely canon gap. |
|
||||
| `UtilityInterface` | `endpoint` when technical, `software-system` when abstract. |
|
||||
| `CostCenter` | `control`; likely canon gap. |
|
||||
| `ProfitCenter` | `control`; likely canon gap. |
|
||||
|
||||
New vNext edge types to map:
|
||||
|
||||
| vNext edge type | Suggested canonical type |
|
||||
| --- | --- |
|
||||
| `contains` | `part_of` |
|
||||
| `owned_by` | `governed_by` |
|
||||
| `operated_by` | `governed_by` |
|
||||
| `provides_utility_to` | `depends_on` with `relationship_category: utility` |
|
||||
| `attributed_to_cost_center` | `governed_by` or canon gap |
|
||||
| `attributed_to_profit_center` | `governed_by` or canon gap |
|
||||
| `evidenced_by` | `evidenced_by` |
|
||||
|
||||
## Examples
|
||||
|
||||
### Current Single Railiance Fabric
|
||||
|
||||
```yaml
|
||||
netkingdom:
|
||||
id: railiance.netkingdom
|
||||
name: Railiance Netkingdom
|
||||
king_actor_id: actor.railiance.king
|
||||
actors:
|
||||
- id: actor.railiance.king
|
||||
kind: FabricActor
|
||||
role: king
|
||||
name: Railiance King
|
||||
- id: actor.railiance.primary-lord
|
||||
kind: FabricActor
|
||||
role: lord
|
||||
name: Railiance Primary Lord
|
||||
fabrics:
|
||||
- id: fabric.railiance.primary
|
||||
kind: Fabric
|
||||
name: Railiance Primary Fabric
|
||||
netkingdom_id: railiance.netkingdom
|
||||
lord_actor_id: actor.railiance.primary-lord
|
||||
parent_fabric_id: null
|
||||
status: active
|
||||
```
|
||||
|
||||
### Future Tenant Subfabric
|
||||
|
||||
```yaml
|
||||
actors:
|
||||
- id: actor.coulomb.tenant
|
||||
kind: FabricActor
|
||||
role: tenant
|
||||
name: Coulomb Tenant
|
||||
fabrics:
|
||||
- id: subfabric.railiance.tenant.coulomb
|
||||
kind: Subfabric
|
||||
name: Coulomb Tenant Subfabric
|
||||
netkingdom_id: railiance.netkingdom
|
||||
parent_fabric_id: fabric.railiance.primary
|
||||
tenant_actor_id: actor.coulomb.tenant
|
||||
status: planned
|
||||
```
|
||||
|
||||
### Cross-Subfabric Utility
|
||||
|
||||
```yaml
|
||||
edges:
|
||||
- id: utility:state-hub-http:coulomb-client
|
||||
from: state-hub.http
|
||||
to: coulomb.automation-client
|
||||
type: provides_utility_to
|
||||
relationship_category: utility
|
||||
provider:
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
fabric_id: fabric.railiance.primary
|
||||
subfabric_id: null
|
||||
consumer:
|
||||
owner_actor_id: actor.coulomb.tenant
|
||||
fabric_id: fabric.railiance.primary
|
||||
subfabric_id: subfabric.railiance.tenant.coulomb
|
||||
boundary:
|
||||
crosses_fabric_boundary: false
|
||||
crosses_subfabric_boundary: true
|
||||
utility:
|
||||
utility_type: coordination_api
|
||||
contract_id: state-hub.http
|
||||
payment_schema_id: payment.internal-tenant-access
|
||||
metering_basis: unknown
|
||||
business_model: tenant_utility
|
||||
```
|
||||
|
||||
## State Hub Import Requirements
|
||||
|
||||
State Hub should materialize at least:
|
||||
|
||||
- export version and schema version;
|
||||
- netkingdom id;
|
||||
- fabric/subfabric ids and names;
|
||||
- actor ids, roles, and names;
|
||||
- node containment fields;
|
||||
- node owner actor and owner role;
|
||||
- ownership resolution state;
|
||||
- edge relationship category;
|
||||
- utility provider/consumer ownership context;
|
||||
- boundary crossing booleans;
|
||||
- cost/profit center attribution;
|
||||
- unresolved ownership and containment gaps;
|
||||
- retained canon metadata and evidence state.
|
||||
|
||||
State Hub must not invent missing ownership or redefine fabric membership. It
|
||||
may display unresolved gaps and expose filters/views for operator review.
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
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.
|
||||
the long-term target is for each owning repo to contribute local evidence while
|
||||
financial Fabric ownership and boundary decisions come from accountability-root
|
||||
discovery.
|
||||
|
||||
## Seeded Repos
|
||||
|
||||
@@ -28,8 +29,9 @@ For each owning repo:
|
||||
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.
|
||||
6. Once accountability-root discovery can reproduce the graph, mark the
|
||||
central seed declarations as bootstrap evidence and keep only the repo-local
|
||||
facts that remain useful self-description.
|
||||
|
||||
## Suggested Order
|
||||
|
||||
|
||||
@@ -89,6 +89,13 @@ Hosts should also include useful optional fields when available: `label`,
|
||||
`freshnessState`, `confidence`, `visualSize`, `ownership`, `unresolved`,
|
||||
`sourceReferences`, and `deepLinks`.
|
||||
|
||||
Fabric hosts should also include deployment overlay fields when available:
|
||||
`deploymentEnvironment`, `deploymentScenario`, `routingAuthority`,
|
||||
`accessZone`, `policyAuthority`, `exposure`, and `host`. These fields describe
|
||||
where an element runs or is reachable in a concrete deployment scenario. They
|
||||
do not define fabric membership and must remain filter/grouping metadata unless
|
||||
the host explicitly promotes a separate graph relationship with evidence.
|
||||
|
||||
Edges may include layout hints used by the client-side layout engine:
|
||||
`sameRepo`, `sourceRepo`, `targetRepo`, `layoutAffinity`,
|
||||
`layoutIdealLength`, and `layoutElasticity`. Fabric uses these hints to keep
|
||||
@@ -97,6 +104,32 @@ edges are intentionally shortest and most elastic; deployment-to-repo edges are
|
||||
longer and looser so infrastructure placement does not collapse into the repo
|
||||
node.
|
||||
|
||||
## Semantic Attractor Modes
|
||||
|
||||
Semantic attractors are view-only topic poles that can pull graph entities
|
||||
toward conceptual neighborhoods in spring-based layouts. For repository maps,
|
||||
an operator might choose attractors such as `security`, `development`, and
|
||||
`operations`; Fabric can then score each repository's semantic closeness to
|
||||
those attractors from repo-owned `SCOPE.md` evidence and map the score to
|
||||
layout strength.
|
||||
|
||||
Attractors are not domain edges and do not change Fabric graph data. They may
|
||||
be materialized as synthetic display-only nodes and `semantic_attraction`
|
||||
edges, or carried as top-level view metadata that the renderer turns into
|
||||
layout forces. Attraction scores should remain inspectable, with source
|
||||
references and confidence, so the operator can understand why a repository was
|
||||
pulled toward a topic.
|
||||
|
||||
Unlike zones, attractors may overlap. A repository can be close to both
|
||||
`development` and `operations`, and the layout should place it between those
|
||||
poles. Zone resolvers, boundary diagnostics, dependency queries, blast-radius
|
||||
queries, and collapsed-zone boundary edges should ignore semantic attraction
|
||||
edges unless a host explicitly promotes an attractor relation into canonical
|
||||
graph data.
|
||||
|
||||
See `docs/semantic-attractors.md` for the concept model, scoring semantics,
|
||||
payload direction, and implementation path.
|
||||
|
||||
## Display State Ownership
|
||||
|
||||
The contract allows either the host service or the engine to evaluate display
|
||||
@@ -132,6 +165,94 @@ The first Fabric manifest declares:
|
||||
| `binding` | Binding assertions between consumer dependencies and providers. |
|
||||
| `library` | Future library/SBOM inventory nodes. |
|
||||
|
||||
## Zone Overlay Modes
|
||||
|
||||
The graph explorer should support zone-oriented modes for Fabric payloads:
|
||||
|
||||
| Mode | Purpose |
|
||||
|------|---------|
|
||||
| `fabric` | Group by financial responsibility: fabric, subfabric, owner. |
|
||||
| `environment` | Group by `dev`, `test`, `prod`, or other deployment environment. |
|
||||
| `deployment-scenario` | Group by concrete scenario such as `bernd-laptop`, `coulombcore`, or `railiance01`. |
|
||||
| `routing-authority` | Group by loopback launcher, Compose port mapping, ingress controller, reverse proxy, DNS, or equivalent route authority. |
|
||||
| `access-zone` | Group by intended reachability such as `private-dev`, `collaborator-test`, `early-access`, `production-public`, or `production-admin`. |
|
||||
|
||||
Zone modes are diagnostic views. They answer "where does this run or who can
|
||||
reach it here?" without mutating the underlying Fabric responsibility boundary.
|
||||
|
||||
Zone boundary overlays are a visual layer over the graph canvas. They should be
|
||||
computed from visible node positions after layout/filtering rather than modeled
|
||||
as graph parent nodes. The overlay should be backed by explicit zone definitions
|
||||
and a resolver so visible nodes are assigned to at most one rendered zone. The
|
||||
default boundary grouping is `deploymentEnvironment`:
|
||||
|
||||
| Overlay label | Matching nodes |
|
||||
|---------------|----------------|
|
||||
| `dev-tegwick` | visible nodes with `deploymentEnvironment: dev` in the private local development overlay |
|
||||
| `test` | visible nodes with `deploymentEnvironment: test` or legacy `staging` |
|
||||
| `prod` | visible nodes with `deploymentEnvironment: prod` |
|
||||
|
||||
Nodes without deployment overlay data are not enclosed. Edge-only deployment
|
||||
overlay evidence may contribute to zone summaries and warnings, but it should
|
||||
not create a boundary unless at least one visible node belongs to the same zone.
|
||||
When access-zone grouping is selected, boundaries use `accessZone` values such
|
||||
as `private-dev`, `collaborator-test`, `production-public`, or
|
||||
`production-admin`.
|
||||
|
||||
Zone labels should be rendered as plain text in the upper-left corner of the
|
||||
zone rectangle, without badge frames or white backgrounds. The label may still
|
||||
act as the zone's focus/drag handle as long as it visually reads as text drawn
|
||||
on the zone surface.
|
||||
|
||||
Useful warnings for the graph explorer include:
|
||||
|
||||
- control surfaces in user-facing access zones;
|
||||
- production nodes with unrestricted developer access;
|
||||
- early-access routes without a policy authority;
|
||||
- services present in production but missing from test;
|
||||
- local-only surfaces that appear in shared or production scenarios;
|
||||
- conflicting port or host claims within the same deployment scenario.
|
||||
|
||||
Zone resolvers should also expose scoped diagnostics in zone detail panels. The
|
||||
initial diagnostic set includes empty zone seed sets, visible nodes matched by
|
||||
multiple zone definitions, and edges crossing zone boundaries. Attraction
|
||||
diagnostics such as multiple attraction candidates or depth-limit stops belong
|
||||
to the same resolver diagnostic channel when attraction rules are enabled.
|
||||
Display-only context edges, such as repository `declares` edges, are evidence
|
||||
for where declarations came from. They must not count as zone boundary
|
||||
connectivity, attraction paths, or collapsed-zone boundary edges unless a host
|
||||
explicitly promotes them to canonical graph relationships.
|
||||
|
||||
Saved graph profiles should persist zone view state as an explicit nested
|
||||
`zone` object. The initial fields are `visible`, `grouping`, `definitionSet`,
|
||||
`presentation`, and `containers`. URL parameters may continue to expose
|
||||
compatibility aliases such as `zoneBoundaries`, `zoneGrouping`, and
|
||||
`zoneDefinitionSet`, but saved profiles should prefer the nested object so
|
||||
future zone definition sets, presentation preferences, and operator-placed zone
|
||||
surfaces can be restored without another state migration.
|
||||
|
||||
Zone containers are view state, not fabric data. A container stores a stable
|
||||
zone surface position and size in graph coordinates. Global graph layout may
|
||||
place unzoned nodes and provide an initial center for new zones, but existing
|
||||
zone containers should keep their operator-chosen positions when the layout
|
||||
algorithm changes. After the global layout pass, each zone may project its
|
||||
assigned visible nodes into local coordinates inside its container. The current
|
||||
local layout choices are compact grid and circle. The selected zone-local
|
||||
layout algorithm belongs in the nested `zone.layout.algorithm` view state so it
|
||||
can be restored by saved or copied views without changing the Fabric payload.
|
||||
|
||||
Zone collapse is a view-only operation. A collapsed zone should hide its visible
|
||||
member nodes, replace them with a synthetic zone node, and draw synthetic
|
||||
boundary edges from that zone node to visible external neighbors. Internal edges
|
||||
are summarized on the zone node rather than rendered. Expanding the zone removes
|
||||
the synthetic elements and restores the original graph elements without
|
||||
changing the underlying payload.
|
||||
|
||||
Zone dragging is also view-only. Dragging a zone handle should translate the
|
||||
currently assigned visible member nodes by the same delta and then recompute the
|
||||
overlay bounds from the new node positions. The operation updates Cytoscape view
|
||||
coordinates only; it does not change Fabric graph data.
|
||||
|
||||
## Repo-Scoping Compatibility
|
||||
|
||||
Repo-scoping can adapt without a rewrite because its current graph payload
|
||||
@@ -167,3 +288,5 @@ Host repos should own:
|
||||
- authentication and authorization
|
||||
- domain-specific graph modes
|
||||
- deep links back to source systems
|
||||
- deployment overlay extraction from the route/proxy/deployment authority they
|
||||
control
|
||||
|
||||
@@ -6,18 +6,38 @@ verified, and how to extract the shared engine once the second adapter is ready.
|
||||
|
||||
## Launch
|
||||
|
||||
Start the registry against the local SQLite database:
|
||||
List available Make targets from the repo root:
|
||||
|
||||
```bash
|
||||
railiance-fabric-registry --db .railiance-fabric/registry.sqlite3 --port 8765
|
||||
make
|
||||
```
|
||||
|
||||
Open the map:
|
||||
Start the registry-backed graph explorer explicitly:
|
||||
|
||||
```bash
|
||||
make graph-explorer
|
||||
```
|
||||
|
||||
This serves the graph explorer at:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8765/ui/graph-explorer
|
||||
```
|
||||
|
||||
The target defaults to `.railiance-fabric/registry.sqlite3`, host
|
||||
`127.0.0.1`, and port `8765`. Override those when needed:
|
||||
|
||||
```bash
|
||||
make graph-explorer PORT=8876
|
||||
make graph-explorer HOST=0.0.0.0 PORT=8765 REGISTRY_DB=/tmp/railiance-fabric.sqlite3
|
||||
```
|
||||
|
||||
Equivalent raw command:
|
||||
|
||||
```bash
|
||||
railiance-fabric-registry --db .railiance-fabric/registry.sqlite3 --port 8765
|
||||
```
|
||||
|
||||
Useful supporting endpoints:
|
||||
|
||||
```text
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# Registry API
|
||||
|
||||
The Railiance Fabric registry is a small HTTP API over repo-owned Fabric
|
||||
declarations and supporting inventory.
|
||||
The Railiance Fabric registry is a small HTTP API over accepted Fabric graph
|
||||
snapshots, discovery evidence, reset archives, and supporting inventory.
|
||||
Legacy repo-local declarations can still produce snapshots, but they are
|
||||
evidence for the graph rather than the long-term authority for external
|
||||
deployment, ownership, tenant, or utility relations.
|
||||
|
||||
## Health And Status
|
||||
|
||||
@@ -27,7 +30,9 @@ GET /repositories/{repo_slug}/snapshots/diff
|
||||
```
|
||||
|
||||
Snapshot ingestion accepts a `FabricGraphExport` under `graph` plus `commit`
|
||||
and optional `generated_at`.
|
||||
and optional `generated_at`. The registry accepts both legacy
|
||||
`railiance.fabric/v1alpha1` graph exports and the financial
|
||||
`railiance.fabric/v1alpha2` / `financial-fabric-v1` export contract.
|
||||
|
||||
## Discovery Snapshots
|
||||
|
||||
@@ -99,4 +104,23 @@ GET /exports/libraries/xregistry
|
||||
GET /ui/graph-explorer
|
||||
GET /exports/graph-explorer/manifest
|
||||
GET /exports/graph-explorer
|
||||
GET /exports/reset-archive
|
||||
```
|
||||
|
||||
`GET /exports/state-hub` currently serves the accepted combined graph for the
|
||||
State Hub read model. During the financial reset, keep using the legacy shape
|
||||
for existing State Hub views until `STATE-WP-0051` materializes v1alpha2
|
||||
fields. Use `railiance-fabric export --format financial` and
|
||||
`examples/exports/financial-fabric-v1.json` as the vNext contract references.
|
||||
|
||||
## Guarded Reset
|
||||
|
||||
```text
|
||||
POST /admin/reset-graph-data
|
||||
```
|
||||
|
||||
The reset endpoint requires `confirm`,
|
||||
`reason`, and `archive_sha256`. `confirm` must be
|
||||
`RESET-RAILIANCE-FABRIC-GRAPH-DATA`. Operators should prefer the CLI wrapper
|
||||
documented in `docs/registry-reset-operations.md`, because it exports the
|
||||
archive and computes the checksum before calling the destructive endpoint.
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
# Registry Onboarding
|
||||
|
||||
Multi-repo onboarding uses a repo-owned manifest to register ecosystem
|
||||
Multi-repo onboarding uses an operator-owned manifest to register ecosystem
|
||||
repositories with a running Railiance Fabric registry and to push graph and
|
||||
library inventory when the local checkout has the required inputs.
|
||||
|
||||
Repo-local declarations remain useful self-description evidence. They should
|
||||
not be treated as the default authority for external fabric membership,
|
||||
ownership, tenant boundaries, or cross-boundary utility relations. Those belong
|
||||
to the accountability-root discovery model described in
|
||||
`docs/FabricDiscoveryAndUpdate.md`.
|
||||
|
||||
## Run The Railiance Manifest
|
||||
|
||||
Start the registry:
|
||||
@@ -57,7 +63,7 @@ registration.
|
||||
Each repository entry is registered first. If the checkout is unavailable or has
|
||||
no Fabric declarations yet, the command leaves the repo registered and reports a
|
||||
warning. This lets the registry represent known repos before every repo has
|
||||
adopted local declarations.
|
||||
published local evidence.
|
||||
|
||||
When declarations exist, the command validates them, builds a graph snapshot,
|
||||
and posts it to:
|
||||
@@ -67,11 +73,18 @@ POST /repositories/{repo_slug}/snapshots
|
||||
```
|
||||
|
||||
The first Railiance manifest keeps the seed ecosystem graph in
|
||||
`railiance-fabric` and registers adjacent repos as known sources. As those repos
|
||||
adopt repo-local `fabric/` declarations, they can be synced without changing the
|
||||
registry API.
|
||||
`railiance-fabric` and registers adjacent repos as known sources. As discovery
|
||||
roots mature, adjacent repos can contribute local evidence without making each
|
||||
repo responsible for the whole external fabric relation model.
|
||||
|
||||
`registry/local-repos.yaml` is a broader local-host manifest. It is generated
|
||||
from active State Hub repo records whose local path exists on the current WSL
|
||||
host. It registers cross-domain local repos into the Fabric registry while only
|
||||
syncing graph snapshots for repos that already have Fabric declarations.
|
||||
|
||||
When two active repo records point at the same checkout path, accountability
|
||||
root discovery treats them as aliases of one repository identity. The canonical
|
||||
identity can be declared with `canonical_slug`; otherwise discovery prefers the
|
||||
slug matching the checkout directory name and then the entry with a remote URL.
|
||||
Use `split_identity: true` only when shared paths are intentional and should
|
||||
remain separate review candidates.
|
||||
|
||||
61
docs/registry-reset-operations.md
Normal file
61
docs/registry-reset-operations.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Registry Reset Operations
|
||||
|
||||
RAIL-FAB-WP-0016 allows a destructive registry graph reset, but only after an
|
||||
archive has been produced and the operator uses an explicit confirmation token.
|
||||
|
||||
## Export Archive Only
|
||||
|
||||
```bash
|
||||
railiance-fabric registry export-reset-archive \
|
||||
.railiance-fabric/reset-archive.json \
|
||||
--registry-url http://127.0.0.1:8765
|
||||
```
|
||||
|
||||
The archive contains:
|
||||
|
||||
- repository registrations,
|
||||
- current combined graph export,
|
||||
- stored graph snapshots,
|
||||
- discovery snapshots and accepted graph snapshot links,
|
||||
- artifacts,
|
||||
- library inventory,
|
||||
- prior reset events,
|
||||
- rollback notes.
|
||||
|
||||
## Guarded Reset
|
||||
|
||||
```bash
|
||||
railiance-fabric registry reset-graph-data \
|
||||
--registry-url http://127.0.0.1:8765 \
|
||||
--archive .railiance-fabric/reset-archive.json \
|
||||
--confirm RESET-RAILIANCE-FABRIC-GRAPH-DATA \
|
||||
--reason "canon-aligned graph reset before full reingest"
|
||||
```
|
||||
|
||||
The command first writes the archive, computes its SHA-256, then calls the
|
||||
registry reset endpoint. The registry records a reset event with the archive
|
||||
path, archive checksum, reason, and dropped row counts.
|
||||
|
||||
The reset deletes graph snapshots, discovery snapshots, artifacts, and library
|
||||
inventory. Repository registration rows are preserved so reingest can start
|
||||
from the known repo list.
|
||||
|
||||
## Rollback Limits
|
||||
|
||||
The archive is a JSON evidence bundle, not an automatic SQLite restore. Use it
|
||||
to inspect or manually reinsert prior registry data if needed. After reset, the
|
||||
intended source of truth is a fresh scan and acceptance pass from accountability
|
||||
roots using the financial Fabric model. The current bridge is the Railiance
|
||||
baseline projection:
|
||||
|
||||
```bash
|
||||
railiance-fabric validate .
|
||||
railiance-fabric export --format financial
|
||||
railiance-fabric registry sync --repo-slug railiance-fabric .
|
||||
```
|
||||
|
||||
Use repo-local declarations as evidence during reingest, not as the authority
|
||||
for all external ownership, tenant, or utility relations.
|
||||
|
||||
Do not run the reset until the replacement scanner/projection path has passed
|
||||
validation and a sample reingest review.
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
The repo reality scanner discovers Fabric entities from repository evidence and
|
||||
turns them into candidate graph facts. It is a discovery layer, not a new
|
||||
authoring surface. Repo-owned declarations remain the highest-trust source for
|
||||
accepted Fabric graph data.
|
||||
authoring surface. Repo-owned declarations remain high-trust self-description
|
||||
evidence, but financial Fabric ownership, tenant boundaries, and cross-boundary
|
||||
utility relations must be resolved from accountability roots or review
|
||||
decisions before they become accepted graph data.
|
||||
|
||||
## Contract
|
||||
|
||||
@@ -76,9 +78,10 @@ returns malformed JSON, the scanner records a `review_artifacts` entry and keeps
|
||||
the discovery snapshot schema-valid.
|
||||
|
||||
The LLM never receives the whole repository. The scanner first builds a compact
|
||||
evidence bundle from deterministic candidates, prioritizing repo-owned Fabric
|
||||
declarations, services, capabilities, interfaces, libraries, deployments, and
|
||||
small README/INTENT/SCOPE signals. The prompt asks for strict JSON:
|
||||
evidence bundle from deterministic candidates, prioritizing durable local
|
||||
evidence such as Fabric declarations, services, capabilities, interfaces,
|
||||
libraries, deployments, and small README/INTENT/SCOPE signals. The prompt asks
|
||||
for strict JSON:
|
||||
|
||||
```json
|
||||
{"nodes": [], "edges": [], "attributes": []}
|
||||
@@ -162,7 +165,9 @@ candidate stable keys. Existing accepted graph nodes win over discovery nodes
|
||||
with the same graph id, so repo-owned declarations are preserved. Projected
|
||||
nodes carry discovery stable key, origin, review state, confidence, provenance,
|
||||
and source anchors in graph attributes; the graph explorer payload exposes
|
||||
those fields for review.
|
||||
those fields for review. For financial Fabric fields, accepted discovery still
|
||||
needs an accountability-root, baseline inheritance rule, or explicit review
|
||||
decision.
|
||||
|
||||
## Connector Follow-Up
|
||||
|
||||
@@ -198,8 +203,9 @@ with status, source, message, and candidate counts.
|
||||
|
||||
Connector-derived facts should be treated this way:
|
||||
|
||||
- accepted: only when the connector reads explicit repo-owned declarations or a
|
||||
catalog already governed as authoritative for that field
|
||||
- accepted: only when the connector reads explicit repo-owned evidence,
|
||||
accountability-root evidence, or a catalog already governed as authoritative
|
||||
for that field
|
||||
- candidate: stable local registry facts such as onboarding manifest entries,
|
||||
declared remote URLs, State Hub ids, and declaration paths
|
||||
- review-only: missing catalogs, rate limits, connector failures, ambiguous
|
||||
@@ -408,9 +414,11 @@ precedence:
|
||||
6. `manual`
|
||||
|
||||
Manual review can override local candidate state, but it should not silently
|
||||
rewrite repo-owned declarations. If accepted discoveries should become
|
||||
authoritative, the safer next step is to generate a repo-owned declaration patch
|
||||
for human review.
|
||||
rewrite repo-owned declarations. If accepted discoveries should become durable
|
||||
repo-local evidence, generate a repo-owned declaration patch for human review.
|
||||
If they affect financial ownership, fabric containment, tenancy, or utility
|
||||
value boundaries, generate a baseline or accountability-root review item
|
||||
instead.
|
||||
|
||||
## Duplicate Handling
|
||||
|
||||
|
||||
340
docs/semantic-attractors.md
Normal file
340
docs/semantic-attractors.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Semantic Attractors
|
||||
|
||||
## Intent
|
||||
|
||||
Semantic attractors are view entities that help an operator orient inside a
|
||||
medium or large graph. An attractor represents a topic, concern, capability
|
||||
area, operating mode, or other conceptual pole such as `security`,
|
||||
`development`, `operations`, `identity`, `data`, or `delivery`.
|
||||
|
||||
The graph explorer can place attractors on the canvas and connect graph
|
||||
entities to them with view-only relationship strength. The stronger an
|
||||
entity's semantic closeness to an attractor, the more that attractor should
|
||||
pull the entity in force-directed or spring-based layouts.
|
||||
|
||||
The first motivating use case is repository orientation. Given a set of
|
||||
repositories, the operator defines attractors such as `security`,
|
||||
`development`, and `operations`. Railiance Fabric reads each repository's
|
||||
`SCOPE.md`, estimates semantic closeness to those attractors, and maps that
|
||||
score to layout force. The resulting map becomes a navigational surface: repos
|
||||
with similar purpose drift toward the same conceptual pole without replacing
|
||||
the underlying dependency or responsibility graph.
|
||||
|
||||
## What Attractors Are
|
||||
|
||||
An attractor is not a fabric node in the source graph. It is a graph-view
|
||||
artifact with these responsibilities:
|
||||
|
||||
- name a topic or concern that is useful for orientation;
|
||||
- define how closeness to that topic is measured;
|
||||
- expose a score for each eligible entity;
|
||||
- translate that score into layout hints and optional visual edges;
|
||||
- keep the scoring evidence inspectable so the map does not become mysterious.
|
||||
|
||||
Attractors should be saved as view/profile configuration, operator presets, or
|
||||
host-provided explorer configuration. They should not mutate repo-owned Fabric
|
||||
declarations, and they should not imply that a repository provides or consumes
|
||||
a capability.
|
||||
|
||||
## Why This Helps
|
||||
|
||||
Dependency edges answer "what depends on what?" Ownership and deployment
|
||||
metadata answer "who owns this?" and "where does this run?" Those questions are
|
||||
necessary, but they can still leave a large repo collection hard to scan.
|
||||
|
||||
Attractors answer a softer question: "what is this near, conceptually?"
|
||||
|
||||
This gives operators a fast way to discover clusters such as:
|
||||
|
||||
- repos that are security-heavy but not obvious from their names;
|
||||
- operations tooling that depends on development systems;
|
||||
- application repos that are unexpectedly close to platform/runtime concerns;
|
||||
- thin adapter repos that sit between two conceptual poles;
|
||||
- orphaned or ambiguous repos that have weak attraction to every known topic.
|
||||
|
||||
## Core Model
|
||||
|
||||
An attractor definition should be serializable and stable:
|
||||
|
||||
```yaml
|
||||
id: security
|
||||
label: Security
|
||||
description: Identity, authorization, secrets, MFA, audit, policy, and trust boundaries.
|
||||
applies_to:
|
||||
layers: [repository]
|
||||
evidence:
|
||||
sources:
|
||||
- type: scope_markdown
|
||||
path: SCOPE.md
|
||||
scoring:
|
||||
method: lexical_semantic_profile
|
||||
anchors:
|
||||
- security
|
||||
- identity
|
||||
- authorization
|
||||
- secrets
|
||||
- audit
|
||||
- policy
|
||||
- mfa
|
||||
negative_anchors:
|
||||
- unrelated
|
||||
normalization:
|
||||
mode: per_entity_softmax
|
||||
layout:
|
||||
min_score: 0.15
|
||||
max_score: 1.0
|
||||
strength_scale: 0.8
|
||||
ideal_length:
|
||||
min: 80
|
||||
max: 420
|
||||
presentation:
|
||||
color: "#be123c"
|
||||
edge_style: dashed
|
||||
```
|
||||
|
||||
The exact schema can evolve, but the responsibilities should remain separate:
|
||||
|
||||
- `applies_to` chooses which graph elements can be scored.
|
||||
- `evidence` declares which text or metadata is used.
|
||||
- `scoring` defines the semantic metric.
|
||||
- `normalization` turns raw scores into comparable view weights.
|
||||
- `layout` maps weights to graph layout hints.
|
||||
- `presentation` controls the optional visual attractor node and edges.
|
||||
|
||||
## Scoring From SCOPE.md
|
||||
|
||||
`SCOPE.md` is a useful first evidence source because it is intentionally short,
|
||||
repo-owned, and written to explain when a repository is relevant. For repository
|
||||
attraction, the scorer should use sections such as:
|
||||
|
||||
- `One-liner`
|
||||
- `Core Idea`
|
||||
- `In Scope`
|
||||
- `Relevant When`
|
||||
- `Provided Capabilities`
|
||||
- `Related / Overlapping Repositories`
|
||||
- `Terminology`
|
||||
|
||||
Sections such as `Out of Scope` and `Not Relevant When` should be used
|
||||
carefully. They can reduce false positives, but they should not erase a topic
|
||||
just because the repo mentions a boundary. For example, a repo can say it is
|
||||
not an authorization engine while still being semantically near security
|
||||
because it models secrets, policy, or trust boundaries.
|
||||
|
||||
The first implementation can use a transparent lexical profile:
|
||||
|
||||
1. Parse `SCOPE.md` into sections.
|
||||
2. Tokenize section text and provided capability keywords.
|
||||
3. Weight section matches, with `One-liner`, `Core Idea`, `In Scope`, and
|
||||
capability keywords carrying more weight than incidental notes.
|
||||
4. Score each attractor by matching configured anchors and related terms.
|
||||
5. Normalize scores per entity so one verbose `SCOPE.md` does not dominate.
|
||||
6. Store the score, confidence, and top evidence snippets in the view payload.
|
||||
|
||||
Later implementations can replace or augment lexical scoring with embeddings,
|
||||
LLM-assisted classification, or operator-reviewed labels. The contract should
|
||||
not depend on a particular scorer.
|
||||
|
||||
## Score Semantics
|
||||
|
||||
Attractor scores should be continuous values in `[0, 1]`.
|
||||
|
||||
Suggested interpretation:
|
||||
|
||||
| Score | Meaning |
|
||||
|-------|---------|
|
||||
| `0.00` | no useful evidence of semantic closeness |
|
||||
| `0.10` to `0.30` | weak signal; useful only as a faint layout hint |
|
||||
| `0.30` to `0.60` | moderate closeness; entity should visibly lean toward the attractor |
|
||||
| `0.60` to `0.85` | strong closeness; entity likely belongs near the attractor cluster |
|
||||
| `0.85` to `1.00` | primary semantic identity or explicit operator label |
|
||||
|
||||
Every score should carry a confidence separate from closeness. A repo with a
|
||||
thin or missing `SCOPE.md` may have low confidence even if a few terms match.
|
||||
|
||||
Attractors should also support multi-attraction. A repository can be close to
|
||||
both `development` and `operations`; the layout should then place it between
|
||||
those poles instead of forcing a single category. This is the main difference
|
||||
from zones: zones preserve a single-surface invariant, while attractors are
|
||||
allowed to overlap because they are layout forces, not containers.
|
||||
|
||||
## Layout Mapping
|
||||
|
||||
Attraction scores become layout hints. They should not become domain edges.
|
||||
|
||||
A graph explorer can map scores to synthetic view edges:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "attractor:security->repo:flex-auth",
|
||||
"source": "attractor:security",
|
||||
"target": "repo:flex-auth",
|
||||
"edgeType": "semantic_attraction",
|
||||
"displayOnly": true,
|
||||
"score": 0.82,
|
||||
"confidence": 0.74,
|
||||
"strength": "strong",
|
||||
"layoutAffinity": 0.82,
|
||||
"layoutIdealLength": 110,
|
||||
"layoutElasticity": 0.9,
|
||||
"sourceReferences": [
|
||||
{
|
||||
"type": "scope_markdown",
|
||||
"path": "SCOPE.md",
|
||||
"section": "In Scope"
|
||||
}
|
||||
]
|
||||
},
|
||||
"classes": "semantic-attraction"
|
||||
}
|
||||
```
|
||||
|
||||
For force-directed layouts:
|
||||
|
||||
- stronger scores should increase spring strength or edge weight;
|
||||
- stronger scores should shorten ideal length;
|
||||
- weak scores may be hidden visually while still applying a small force;
|
||||
- edges below a configured threshold should not affect layout;
|
||||
- display-only attraction edges should be excluded from dependency, boundary,
|
||||
blast-radius, and zone-connectivity diagnostics.
|
||||
|
||||
Attractor nodes can be pinned, arranged on a ring, placed by the operator, or
|
||||
computed from the current profile. For first use, a stable radial placement is
|
||||
usually enough: place three to eight attractors around the graph, then let
|
||||
repositories find their balance.
|
||||
|
||||
## View Payload Shape
|
||||
|
||||
The graph explorer payload should be able to carry attractor metadata without
|
||||
changing the canonical Fabric graph.
|
||||
|
||||
Recommended top-level view extension:
|
||||
|
||||
```json
|
||||
{
|
||||
"view": {
|
||||
"attractors": {
|
||||
"enabled": true,
|
||||
"definitionSet": "repo-concerns-v1",
|
||||
"definitions": [
|
||||
{
|
||||
"id": "security",
|
||||
"label": "Security",
|
||||
"description": "Identity, authorization, secrets, audit, and policy.",
|
||||
"color": "#be123c"
|
||||
}
|
||||
],
|
||||
"scores": [
|
||||
{
|
||||
"attractor_id": "security",
|
||||
"element_id": "repo:flex-auth",
|
||||
"score": 0.82,
|
||||
"confidence": 0.74,
|
||||
"method": "lexical_semantic_profile",
|
||||
"evidence": [
|
||||
{
|
||||
"source": "SCOPE.md",
|
||||
"section": "Core Idea",
|
||||
"terms": ["authorization", "policy"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The renderer may choose to materialize these into synthetic nodes and edges at
|
||||
runtime. A host may also emit synthetic display-only elements directly if that
|
||||
is easier for the current engine.
|
||||
|
||||
## Operator Workflow
|
||||
|
||||
A useful attractor workflow should feel like mapmaking:
|
||||
|
||||
1. Choose a preset such as `Security / Development / Operations`.
|
||||
2. Review the generated scores and evidence for a few known repos.
|
||||
3. Hide or pin attractors that are not useful for the current question.
|
||||
4. Save the attractor definition set in the graph profile.
|
||||
5. Use the resulting layout to discover ambiguous, central, or misplaced repos.
|
||||
|
||||
The UI should expose:
|
||||
|
||||
- a toggle for semantic attractors;
|
||||
- a definition-set selector;
|
||||
- score threshold controls;
|
||||
- optional visual attraction edges;
|
||||
- pinned/unpinned attractor placement;
|
||||
- detail panels explaining why a repo is close to an attractor;
|
||||
- diagnostics for missing evidence, low confidence, and overly broad
|
||||
attractors.
|
||||
|
||||
## Relationship To Zones
|
||||
|
||||
Zones and attractors solve different orientation problems.
|
||||
|
||||
Zones are bounded drawing surfaces. A visible node belongs to zero or one zone
|
||||
in a given view. They are useful for deployment environments, access zones,
|
||||
ownership surfaces, and other container-like questions.
|
||||
|
||||
Attractors are semantic force points. A visible node can be pulled by multiple
|
||||
attractors at once. They are useful for topical orientation, concern mapping,
|
||||
and discovering conceptual neighborhoods.
|
||||
|
||||
The two concepts can combine cleanly:
|
||||
|
||||
- zones can show where entities run;
|
||||
- attractors can pull repos inside or outside those zones based on semantic
|
||||
concern;
|
||||
- zone diagnostics should ignore semantic attraction edges unless explicitly
|
||||
configured otherwise;
|
||||
- attractor scores can be summarized inside zone details.
|
||||
|
||||
## Initial Presets
|
||||
|
||||
A first repository-orientation preset should keep the set small:
|
||||
|
||||
| Attractor | Topic Signal |
|
||||
|-----------|--------------|
|
||||
| `security` | identity, secrets, authorization, policy, audit, MFA, trust boundaries |
|
||||
| `development` | source code, build, CI/CD, package publishing, scaffolding, developer workflows |
|
||||
| `operations` | deployment, runtime, monitoring, backups, incidents, infrastructure lifecycle |
|
||||
|
||||
Useful follow-up presets:
|
||||
|
||||
- `data`, `identity`, `delivery`, `governance`
|
||||
- `platform`, `application`, `tooling`
|
||||
- `financial`, `runtime`, `coordination`
|
||||
|
||||
Attractors should start as operator-chosen presets rather than global truth.
|
||||
The same repository can be viewed through different conceptual lenses.
|
||||
|
||||
## Implementation Path
|
||||
|
||||
The concept can be implemented incrementally:
|
||||
|
||||
1. Add an attractor definition format for graph explorer profiles.
|
||||
2. Parse repo `SCOPE.md` files during registry sync or graph export.
|
||||
3. Compute transparent lexical scores for repositories.
|
||||
4. Include attractor scores and evidence in the graph explorer payload.
|
||||
5. Add synthetic attractor nodes and display-only attraction edges in the UI.
|
||||
6. Map attraction scores to layout hints for the force-directed layout.
|
||||
7. Add detail-panel evidence and low-confidence diagnostics.
|
||||
8. Support saved attractor presets and operator score overrides.
|
||||
|
||||
This keeps attractors as a view concern until the scoring model proves useful.
|
||||
If a semantic relation becomes durable domain knowledge, it can later be
|
||||
promoted into a proper Fabric declaration with separate evidence and review.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should attractor definitions live in graph profiles, repo config, or a shared
|
||||
registry preset file?
|
||||
- Should scoring run during registry sync, export, or entirely in the browser?
|
||||
- How much operator override should be allowed before scores become maintained
|
||||
labels rather than computed evidence?
|
||||
- What is the right default for missing or stale `SCOPE.md` evidence?
|
||||
- Should the first implementation use only lexical scoring, or should it also
|
||||
prepare a pluggable embedding scorer interface?
|
||||
@@ -1,26 +1,27 @@
|
||||
# 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.
|
||||
Railiance Fabric is the discovery, validation, and versioning layer for the
|
||||
Railiance infrastructure-responsibility graph. 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 Fabric topology,
|
||||
ownership, fabric membership, or cross-boundary utility relations.
|
||||
|
||||
## 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 |
|
||||
| Deployment/accountability roots | Durable evidence of infrastructure, deployment, ownership, utility, and payment responsibility | State Hub task state |
|
||||
| Participating repos | Self-description evidence such as code, manifests, API contracts, package metadata, and legacy `fabric/` declarations | All external deployment/fabric relations |
|
||||
| Railiance Fabric | Schemas, discovery, validation, graph construction, accepted snapshots, exports | State Hub tasks/progress/decisions |
|
||||
| State Hub | Read-model storage, links to repos/workstreams/tasks/progress, dashboard/search views | Editing Fabric topology or inventing ownership |
|
||||
|
||||
The flow is:
|
||||
|
||||
```text
|
||||
repo-local fabric/*.yaml
|
||||
accountability roots + durable deployment evidence + repo evidence
|
||||
|
|
||||
v
|
||||
railiance-fabric validate/export
|
||||
railiance-fabric scan/validate/export
|
||||
|
|
||||
v
|
||||
State Hub graph read model
|
||||
@@ -31,15 +32,25 @@ dashboard, search, planning, progress links
|
||||
|
||||
## Export Shape
|
||||
|
||||
The CLI emits `FabricGraphExport` JSON:
|
||||
The CLI and registry emit `FabricGraphExport` JSON:
|
||||
|
||||
```bash
|
||||
railiance-fabric export --format json
|
||||
railiance-fabric export --format financial
|
||||
```
|
||||
|
||||
Schema: `schemas/state-hub-export.schema.yaml`
|
||||
|
||||
Top-level shape:
|
||||
The schema now accepts two export families:
|
||||
|
||||
- `railiance.fabric/v1alpha1`: legacy declaration-centered graph export.
|
||||
- `railiance.fabric/v1alpha2` with `schema_version:
|
||||
financial-fabric-v1`: financial Fabric graph export.
|
||||
|
||||
### Legacy v1alpha1 Shape
|
||||
|
||||
The legacy top-level shape remains supported for compatibility with
|
||||
`STATE-WP-0050`:
|
||||
|
||||
```yaml
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
@@ -58,6 +69,10 @@ Node fields:
|
||||
| `repo` | Owning repo slug. |
|
||||
| `domain` | Owning domain slug. |
|
||||
| `lifecycle` | Declaration lifecycle. |
|
||||
| `canon_category` | Canon-aligned entity category when known. |
|
||||
| `canon_anchor` | Canon surface that owns the selected category. |
|
||||
| `mapping_fit` | Mapping confidence bucket: `direct`, `partial`, `conflict`, `gap`, or `unknown`. |
|
||||
| `evidence_state` | Evidence state for the node claim: `observed`, `declared`, `inferred`, `proposed`, or `gap`. |
|
||||
|
||||
Edge fields:
|
||||
|
||||
@@ -65,7 +80,87 @@ Edge fields:
|
||||
|-------|---------|
|
||||
| `from` | Source node id. |
|
||||
| `to` | Target node id. |
|
||||
| `type` | Relationship type, such as `provides`, `exposes`, `available_via`, `consumes`, `binds:exact`, or `uses_interface`. |
|
||||
| `type` | Fabric relationship type, such as `provides`, `exposes`, `available_via`, `consumes`, `binds:exact`, or `uses_interface`. |
|
||||
| `canonical_type` | Canon-aligned relationship family when known, such as `exposes`, `depends_on`, `deploys`, or `flows_to`. |
|
||||
| `display_only` | `true` when the edge is a visualization/layout relationship rather than a canonical graph claim. |
|
||||
| `evidence_state` | Evidence state for the claim: `observed`, `declared`, `inferred`, `proposed`, or `gap`. |
|
||||
|
||||
### Financial v1alpha2 Shape
|
||||
|
||||
The financial Fabric export adds first-class responsibility and value
|
||||
semantics:
|
||||
|
||||
```yaml
|
||||
apiVersion: railiance.fabric/v1alpha2
|
||||
kind: FabricGraphExport
|
||||
schema_version: financial-fabric-v1
|
||||
netkingdom:
|
||||
id: railiance.netkingdom
|
||||
king_actor_id: actor.railiance.king
|
||||
actors: []
|
||||
fabrics: []
|
||||
nodes: []
|
||||
edges: []
|
||||
unresolved: []
|
||||
```
|
||||
|
||||
Additional top-level sections:
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `schema_version` | Financial Fabric export schema identity. |
|
||||
| `netkingdom` | Root responsibility context and king actor. |
|
||||
| `actors` | King, lord, tenant, operator, and steward actors. |
|
||||
| `fabrics` | Fabric and subfabric boundaries. |
|
||||
| `unresolved` | Ownership, containment, or import gaps to display for review. |
|
||||
|
||||
Additional node fields:
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `containment` | Netkingdom, fabric, optional subfabric, environment, and optional deployment scenario. |
|
||||
| `ownership` | Owner actor, owner role, and ownership resolution state. |
|
||||
| `accounting` | Optional cost/profit center and allocation metadata. |
|
||||
| `evidence` | Evidence state, review state, confidence, and evidence references. |
|
||||
|
||||
Additional edge fields:
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `relationship_category` | `containment`, `ownership`, `technical`, `utility`, `accounting`, or `evidence`. |
|
||||
| `provider` / `consumer` | Owner and boundary context for utility edges. |
|
||||
| `boundary` | Whether the edge crosses fabric or subfabric boundaries. |
|
||||
| `utility` | Utility type, contract, payment schema, metering basis, and business model. |
|
||||
| `accounting` | Optional cost/profit attribution for the relationship. |
|
||||
| `evidence` | Evidence state, review state, confidence, and evidence references. |
|
||||
|
||||
Example payload: `examples/exports/financial-fabric-v1.json`.
|
||||
|
||||
## Operator Refresh During Reset
|
||||
|
||||
For the current reset, operators should produce both export families:
|
||||
|
||||
```bash
|
||||
railiance-fabric validate .
|
||||
railiance-fabric export --format json
|
||||
railiance-fabric export --format financial
|
||||
```
|
||||
|
||||
The `json` export remains the compatibility payload for State Hub graph views
|
||||
implemented by `STATE-WP-0050`. The `financial` export is the vNext contract
|
||||
artifact for `STATE-WP-0051` and should be reviewed whenever the baseline,
|
||||
discovery inputs, ownership model, or registry materialization changes.
|
||||
|
||||
After workplan file changes, refresh State Hub's file-backed index from the
|
||||
State Hub repo:
|
||||
|
||||
```bash
|
||||
make fix-consistency REPO=railiance-fabric
|
||||
```
|
||||
|
||||
Graph import and workplan consistency are separate. The consistency command
|
||||
does not author Fabric graph topology; it only keeps State Hub's workplan cache
|
||||
aligned with repo files.
|
||||
|
||||
## Proposed State Hub Read Model
|
||||
|
||||
@@ -119,9 +214,20 @@ fabric_graph_edges
|
||||
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.
|
||||
The `STATE-WP-0050` implementation already stores a v1alpha1 read model. The
|
||||
`STATE-WP-0051` follow-up should extend that storage with:
|
||||
|
||||
- import/export schema version;
|
||||
- netkingdom id;
|
||||
- fabric and subfabric ids;
|
||||
- actor ids and roles;
|
||||
- node containment;
|
||||
- node owner actor, owner role, and ownership resolution;
|
||||
- edge relationship category;
|
||||
- utility provider/consumer ownership context;
|
||||
- fabric/subfabric boundary crossing flags;
|
||||
- node and edge accounting attribution;
|
||||
- unresolved ownership or containment gaps.
|
||||
|
||||
## Linking To Existing State Hub Entities
|
||||
|
||||
@@ -133,7 +239,7 @@ State Hub should enrich graph nodes by matching:
|
||||
- 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.
|
||||
Fabric export or source evidence.
|
||||
|
||||
## Ingestion Rules
|
||||
|
||||
@@ -143,7 +249,22 @@ repo-owned declaration files.
|
||||
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.
|
||||
but do not auto-edit Fabric topology, ownership, or source evidence.
|
||||
6. For `v1alpha2`, preserve unresolved ownership/containment gaps rather than
|
||||
inventing State Hub-owned values.
|
||||
7. For `v1alpha2`, expose cost/profit center fields as accounting views, not as
|
||||
fabric membership changes.
|
||||
|
||||
## Current State Hub Limitations
|
||||
|
||||
As of `STATE-WP-0050`, State Hub imports the legacy `v1alpha1` export as a
|
||||
read model. It does not yet materialize the `v1alpha2` financial Fabric fields.
|
||||
|
||||
Until `STATE-WP-0051` is implemented, a `v1alpha2` export is a contract and
|
||||
registry capability in `railiance-fabric`, not a fully queryable State Hub read
|
||||
model. Operators should keep importing the current `v1alpha1` export for
|
||||
existing dashboard/query behavior and use `v1alpha2` payloads for contract
|
||||
verification and follow-on implementation.
|
||||
|
||||
## Initial Dashboard Queries
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ Machine-readable catalog files:
|
||||
|------|-----------|----------|--------------|
|
||||
| `http-api` | active | api | `none`, `oidc`, `jwt`, `mtls`, `api_key` |
|
||||
| `web-ui` | active | ui | `none`, `oidc`, `jwt`, `mtls`, `api_key` |
|
||||
| `mcp-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` |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Declaration Fixtures
|
||||
|
||||
These fixtures support the T02 schema baseline and give the future validator
|
||||
real inputs to exercise.
|
||||
These fixtures support the legacy `v1alpha1` declaration schema and give the
|
||||
validator real inputs to exercise.
|
||||
|
||||
`valid/` contains a coherent mini-graph:
|
||||
|
||||
@@ -11,5 +11,10 @@ real inputs to exercise.
|
||||
- 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.
|
||||
`invalid/` contains schema-level failures. The validator should report clear
|
||||
errors for these before it attempts graph-level checks.
|
||||
|
||||
For the financial Fabric contract, use
|
||||
`examples/exports/financial-fabric-v1.json`. That fixture exercises
|
||||
king/lord/tenant actors, fabric/subfabric containment, ownership, cost/profit
|
||||
attribution, and a cross-boundary utility edge.
|
||||
|
||||
104
examples/discovery/accountability-root-manifest.yaml
Normal file
104
examples/discovery/accountability-root-manifest.yaml
Normal file
@@ -0,0 +1,104 @@
|
||||
apiVersion: railiance.fabric/v1alpha2
|
||||
kind: AccountabilityRootManifest
|
||||
metadata:
|
||||
id: example.accountability-roots
|
||||
name: Example Accountability Roots
|
||||
description: Minimal example showing how a tenant subfabric can be added without changing the parent fabric criterion.
|
||||
netkingdom:
|
||||
id: example.netkingdom
|
||||
name: Example Netkingdom
|
||||
king_actor_id: actor.example.king
|
||||
actors:
|
||||
- id: actor.example.king
|
||||
role: king
|
||||
name: Example King
|
||||
authority:
|
||||
recovery_authority: true
|
||||
secrets_authority: true
|
||||
backup_authority: true
|
||||
termination_authority: true
|
||||
- id: actor.example.lord
|
||||
role: lord
|
||||
name: Example Lord
|
||||
- id: actor.example.tenant
|
||||
role: tenant
|
||||
name: Example Tenant
|
||||
fabrics:
|
||||
- id: fabric.example.primary
|
||||
kind: Fabric
|
||||
name: Example Primary Fabric
|
||||
netkingdom_id: example.netkingdom
|
||||
lord_actor_id: actor.example.lord
|
||||
parent_fabric_id: null
|
||||
status: active
|
||||
boundary:
|
||||
boundary_type: fabric
|
||||
criterion: financial_and_operational_accountability
|
||||
payment_responsibility: actor.example.lord
|
||||
operational_responsibility: actor.example.king
|
||||
recovery_responsibility: actor.example.king
|
||||
- id: subfabric.example.tenant
|
||||
kind: Subfabric
|
||||
name: Example Tenant Subfabric
|
||||
netkingdom_id: example.netkingdom
|
||||
tenant_actor_id: actor.example.tenant
|
||||
parent_fabric_id: fabric.example.primary
|
||||
status: planned
|
||||
boundary:
|
||||
boundary_type: subfabric
|
||||
criterion: restricted_paid_tenant_utility
|
||||
payment_responsibility: actor.example.tenant
|
||||
operational_responsibility: actor.example.lord
|
||||
recovery_responsibility: actor.example.king
|
||||
discovery_roots:
|
||||
- id: root.example.state-hub
|
||||
type: state_hub_repo_inventory
|
||||
status: active
|
||||
fabric_id: fabric.example.primary
|
||||
owner_actor_id: actor.example.king
|
||||
source:
|
||||
base_url: http://127.0.0.1:8000
|
||||
api_paths:
|
||||
- /managed-repos/
|
||||
safe_discovery: metadata_only
|
||||
evidence_scope:
|
||||
- repo_inventory
|
||||
- repository_identity
|
||||
refresh:
|
||||
cadence: on_change
|
||||
triggers:
|
||||
- state_hub_repo_inventory_change
|
||||
- operator_request
|
||||
- id: root.example.tenant-api-contracts
|
||||
type: endpoint_contract
|
||||
status: planned
|
||||
fabric_id: fabric.example.primary
|
||||
subfabric_id: subfabric.example.tenant
|
||||
owner_actor_id: actor.example.tenant
|
||||
source:
|
||||
repo_slug: example-tenant
|
||||
path: contracts/openapi
|
||||
safe_discovery: local_files
|
||||
evidence_scope:
|
||||
- endpoint_contract
|
||||
- tenant_boundary
|
||||
refresh:
|
||||
cadence: on_change
|
||||
triggers:
|
||||
- endpoint_contract_change
|
||||
- operator_request
|
||||
refresh:
|
||||
cadence: manual
|
||||
triggers:
|
||||
- operator_request
|
||||
- state_hub_repo_inventory_change
|
||||
- endpoint_contract_change
|
||||
- lord_or_tenant_change
|
||||
templates:
|
||||
future_subfabric:
|
||||
parent_fabric_id: fabric.example.primary
|
||||
tenant_actor_role: tenant
|
||||
required_updates:
|
||||
- Add tenant actor.
|
||||
- Add Subfabric with tenant_actor_id.
|
||||
- Add subfabric-scoped discovery roots.
|
||||
192
examples/exports/financial-fabric-v1.json
Normal file
192
examples/exports/financial-fabric-v1.json
Normal file
@@ -0,0 +1,192 @@
|
||||
{
|
||||
"apiVersion": "railiance.fabric/v1alpha2",
|
||||
"kind": "FabricGraphExport",
|
||||
"schema_version": "financial-fabric-v1",
|
||||
"generated_at": "2026-05-24T00:00:00Z",
|
||||
"source": {
|
||||
"producer": "railiance-fabric",
|
||||
"registry": "registry",
|
||||
"commit": "example",
|
||||
"generation_reason": "operator_refresh"
|
||||
},
|
||||
"compatibility": {
|
||||
"legacy_v1alpha1_supported": true,
|
||||
"breaking_reset": false
|
||||
},
|
||||
"netkingdom": {
|
||||
"id": "railiance.netkingdom",
|
||||
"name": "Railiance Netkingdom",
|
||||
"king_actor_id": "actor.railiance.king"
|
||||
},
|
||||
"actors": [
|
||||
{
|
||||
"id": "actor.railiance.king",
|
||||
"kind": "FabricActor",
|
||||
"role": "king",
|
||||
"name": "Railiance King"
|
||||
},
|
||||
{
|
||||
"id": "actor.railiance.primary-lord",
|
||||
"kind": "FabricActor",
|
||||
"role": "lord",
|
||||
"name": "Railiance Primary Lord"
|
||||
},
|
||||
{
|
||||
"id": "actor.coulomb.tenant",
|
||||
"kind": "FabricActor",
|
||||
"role": "tenant",
|
||||
"name": "Coulomb Tenant"
|
||||
}
|
||||
],
|
||||
"fabrics": [
|
||||
{
|
||||
"id": "fabric.railiance.primary",
|
||||
"kind": "Fabric",
|
||||
"name": "Railiance Primary Fabric",
|
||||
"netkingdom_id": "railiance.netkingdom",
|
||||
"lord_actor_id": "actor.railiance.primary-lord",
|
||||
"parent_fabric_id": null,
|
||||
"status": "active",
|
||||
"boundary": {
|
||||
"boundary_type": "fabric",
|
||||
"criterion": "financial_and_operational_accountability"
|
||||
},
|
||||
"evidence_refs": []
|
||||
},
|
||||
{
|
||||
"id": "subfabric.railiance.tenant.coulomb",
|
||||
"kind": "Subfabric",
|
||||
"name": "Coulomb Tenant Subfabric",
|
||||
"netkingdom_id": "railiance.netkingdom",
|
||||
"parent_fabric_id": "fabric.railiance.primary",
|
||||
"tenant_actor_id": "actor.coulomb.tenant",
|
||||
"status": "planned",
|
||||
"boundary": {
|
||||
"boundary_type": "subfabric",
|
||||
"criterion": "restricted_paid_tenant_utility"
|
||||
},
|
||||
"evidence_refs": []
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "state-hub.http",
|
||||
"kind": "UtilityInterface",
|
||||
"name": "State Hub HTTP API",
|
||||
"repo": "state-hub",
|
||||
"domain": "custodian",
|
||||
"lifecycle": "active",
|
||||
"containment": {
|
||||
"netkingdom_id": "railiance.netkingdom",
|
||||
"fabric_id": "fabric.railiance.primary",
|
||||
"subfabric_id": null,
|
||||
"environment": "local",
|
||||
"deployment_scenario_id": null
|
||||
},
|
||||
"ownership": {
|
||||
"owner_actor_id": "actor.railiance.primary-lord",
|
||||
"owner_role": "lord",
|
||||
"resolution": "inherited",
|
||||
"inherited_from": "fabric.railiance.primary",
|
||||
"supporting_actor_ids": []
|
||||
},
|
||||
"accounting": {
|
||||
"cost_center_id": "cc.platform.shared",
|
||||
"allocation_model": "direct"
|
||||
},
|
||||
"evidence": {
|
||||
"state": "declared",
|
||||
"review_state": "accepted",
|
||||
"confidence": 0.9,
|
||||
"refs": []
|
||||
},
|
||||
"canon_category": "endpoint",
|
||||
"canon_anchor": "model/network",
|
||||
"mapping_fit": "partial",
|
||||
"evidence_state": "declared",
|
||||
"attributes": {
|
||||
"description": "Example tenant-facing State Hub API utility."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coulomb.automation-client",
|
||||
"kind": "Service",
|
||||
"name": "Coulomb Automation Client",
|
||||
"repo": "coulomb-automation",
|
||||
"domain": "railiance",
|
||||
"lifecycle": "planned",
|
||||
"containment": {
|
||||
"netkingdom_id": "railiance.netkingdom",
|
||||
"fabric_id": "fabric.railiance.primary",
|
||||
"subfabric_id": "subfabric.railiance.tenant.coulomb",
|
||||
"environment": "local",
|
||||
"deployment_scenario_id": null
|
||||
},
|
||||
"ownership": {
|
||||
"owner_actor_id": "actor.coulomb.tenant",
|
||||
"owner_role": "tenant",
|
||||
"resolution": "explicit",
|
||||
"supporting_actor_ids": []
|
||||
},
|
||||
"accounting": {
|
||||
"cost_center_id": "cc.coulomb.automation",
|
||||
"allocation_model": "direct"
|
||||
},
|
||||
"evidence": {
|
||||
"state": "declared",
|
||||
"review_state": "accepted",
|
||||
"confidence": 0.8,
|
||||
"refs": []
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "utility:state-hub-http:coulomb-client",
|
||||
"from": "state-hub.http",
|
||||
"to": "coulomb.automation-client",
|
||||
"type": "provides_utility_to",
|
||||
"relationship_category": "utility",
|
||||
"canonical_type": "depends_on",
|
||||
"canon_anchor": "model/landscape",
|
||||
"mapping_fit": "partial",
|
||||
"display_only": false,
|
||||
"evidence_state": "declared",
|
||||
"provider": {
|
||||
"owner_actor_id": "actor.railiance.primary-lord",
|
||||
"fabric_id": "fabric.railiance.primary",
|
||||
"subfabric_id": null
|
||||
},
|
||||
"consumer": {
|
||||
"owner_actor_id": "actor.coulomb.tenant",
|
||||
"fabric_id": "fabric.railiance.primary",
|
||||
"subfabric_id": "subfabric.railiance.tenant.coulomb"
|
||||
},
|
||||
"boundary": {
|
||||
"crosses_fabric_boundary": false,
|
||||
"crosses_subfabric_boundary": true
|
||||
},
|
||||
"utility": {
|
||||
"utility_type": "coordination_api",
|
||||
"contract_id": "state-hub.http",
|
||||
"payment_schema_id": "payment.internal-tenant-access",
|
||||
"metering_basis": "unknown",
|
||||
"business_model": "tenant_utility"
|
||||
},
|
||||
"accounting": {
|
||||
"provider_profit_center_id": "pc.tenant-utilities",
|
||||
"consumer_cost_center_id": "cc.coulomb.automation",
|
||||
"allocation_model": "usage_weighted"
|
||||
},
|
||||
"evidence": {
|
||||
"state": "declared",
|
||||
"review_state": "accepted",
|
||||
"confidence": 0.8,
|
||||
"refs": []
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
],
|
||||
"unresolved": []
|
||||
}
|
||||
3572
exports/state-hub/2026-05-24-railiance-financial-fabric-v1.json
Normal file
3572
exports/state-hub/2026-05-24-railiance-financial-fabric-v1.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
||||
# Seed Declarations
|
||||
|
||||
These declarations are the first repo-local seed graph for Railiance Fabric.
|
||||
They are legacy `v1alpha1` evidence used for validation, registry snapshots,
|
||||
and compatibility exports.
|
||||
|
||||
They model current and planned Railiance ecosystem providers:
|
||||
|
||||
@@ -17,4 +19,9 @@ They model current and planned Railiance ecosystem providers:
|
||||
|
||||
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.
|
||||
already published Fabric evidence.
|
||||
|
||||
The current financial responsibility baseline lives in
|
||||
`fabric/financial/railiance-netkingdom.yaml`. It defines the Railiance
|
||||
netkingdom, king, current lord, one active fabric, inherited node ownership,
|
||||
and the template for future tenant subfabrics.
|
||||
|
||||
16
fabric/bindings/railiance-apps-artifact-evidence-forge.yaml
Normal file
16
fabric/bindings/railiance-apps-artifact-evidence-forge.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: BindingAssertion
|
||||
metadata:
|
||||
id: railiance-apps.s5-releases.artifact-evidence-to-forge
|
||||
name: S5 artifact evidence binding
|
||||
owner: railiance-apps
|
||||
repo: railiance-apps
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
dependency_id: railiance-apps.s5-releases.needs-artifact-evidence
|
||||
provider_capability_id: railiance-forge.source-forge.artifact-promotion-evidence
|
||||
provider_interface_id: railiance-forge.source-forge.evidence-contract
|
||||
status: compatible
|
||||
rationale: S5 release readiness should cite forge-owned artifact publish, restore, and operating evidence.
|
||||
16
fabric/bindings/railiance-apps-container-registry-forge.yaml
Normal file
16
fabric/bindings/railiance-apps-container-registry-forge.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: BindingAssertion
|
||||
metadata:
|
||||
id: railiance-apps.s5-releases.container-registry-to-forge
|
||||
name: S5 container registry binding
|
||||
owner: railiance-apps
|
||||
repo: railiance-apps
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
dependency_id: railiance-apps.s5-releases.needs-container-registry
|
||||
provider_capability_id: railiance-forge.source-forge.container-registry
|
||||
provider_interface_id: railiance-forge.source-forge.oci-registry
|
||||
status: compatible
|
||||
rationale: S5 releases consume already-published app images from the forge-owned OCI registry.
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: BindingAssertion
|
||||
metadata:
|
||||
id: railiance-enablement.delivery-templates.runner-substrate-to-forge
|
||||
name: Enablement runner substrate binding
|
||||
owner: railiance-enablement
|
||||
repo: railiance-enablement
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
dependency_id: railiance-enablement.delivery-templates.needs-runner-substrate
|
||||
provider_capability_id: railiance-forge.source-forge.workflow-runner-substrate
|
||||
provider_interface_id: railiance-forge.source-forge.runner-label-contract
|
||||
status: compatible
|
||||
rationale: S4 reusable templates should consume forge-owned runner labels, trust posture, and runner evidence.
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: BindingAssertion
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.kubernetes-runtime-to-cluster
|
||||
name: Forge Kubernetes runtime binding
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
dependency_id: railiance-forge.source-forge.needs-kubernetes-runtime
|
||||
provider_capability_id: railiance-cluster.kubernetes.runtime
|
||||
provider_interface_id: railiance-cluster.kubernetes.api
|
||||
status: compatible
|
||||
rationale: The forge runtime is deployed on the Railiance Kubernetes runtime provided by railiance-cluster.
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: BindingAssertion
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.object-storage-to-artifact-store
|
||||
name: Forge object storage binding
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
dependency_id: railiance-forge.source-forge.needs-object-storage
|
||||
provider_capability_id: artifact-store.object-storage
|
||||
provider_interface_id: artifact-store.object-storage.bucket
|
||||
status: compatible
|
||||
rationale: Durable forge artifact/blob preservation should use the planned Railiance object-storage provider rather than ad hoc forge-local storage.
|
||||
16
fabric/bindings/railiance-forge-postgresql-cnpg.yaml
Normal file
16
fabric/bindings/railiance-forge-postgresql-cnpg.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: BindingAssertion
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.postgresql-to-cnpg
|
||||
name: Forge PostgreSQL binding
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
dependency_id: railiance-forge.source-forge.needs-postgresql
|
||||
provider_capability_id: railiance-platform.cnpg.postgresql
|
||||
provider_interface_id: railiance-platform.cnpg.database-connection
|
||||
status: compatible
|
||||
rationale: Current Gitea database state is backed by the Railiance platform CNPG PostgreSQL service.
|
||||
16
fabric/bindings/railiance-forge-runtime-secrets-openbao.yaml
Normal file
16
fabric/bindings/railiance-forge-runtime-secrets-openbao.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: BindingAssertion
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.runtime-secrets-to-openbao
|
||||
name: Forge runtime secrets binding
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
dependency_id: railiance-forge.source-forge.needs-runtime-secrets
|
||||
provider_capability_id: railiance-platform.openbao.runtime-secrets
|
||||
provider_interface_id: railiance-platform.openbao.kv-v2
|
||||
status: compatible
|
||||
rationale: Runtime secret custody for forge workloads belongs to the platform OpenBao path; SOPS/age remains bootstrap only.
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: CapabilityDeclaration
|
||||
metadata:
|
||||
id: railiance-cluster.kubernetes.runtime
|
||||
name: Kubernetes runtime
|
||||
owner: railiance-cluster
|
||||
repo: railiance-cluster
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Cluster scope
|
||||
path: /home/worsch/railiance-cluster/SCOPE.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Provides Kubernetes runtime primitives and API access consumed by Railiance platform, forge, and app workloads.
|
||||
capability_type: kubernetes-runtime
|
||||
service_id: railiance-cluster.kubernetes
|
||||
interface_ids:
|
||||
- railiance-cluster.kubernetes.api
|
||||
criticality: critical
|
||||
data_classification: restricted
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: CapabilityDeclaration
|
||||
metadata:
|
||||
id: railiance-enablement.delivery-templates.ci-cd-templates
|
||||
name: CI/CD workflow templates
|
||||
owner: railiance-enablement
|
||||
repo: railiance-enablement
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Enablement scope
|
||||
path: /home/worsch/railiance-enablement/SCOPE.md
|
||||
- label: Enablement intent
|
||||
path: /home/worsch/railiance-enablement/INTENT.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
description: Reusable Railiance workflow templates, promotion conventions, and delivery gates that consume forge runner labels and artifact evidence.
|
||||
capability_type: ci-cd-template-catalog
|
||||
service_id: railiance-enablement.delivery-templates
|
||||
interface_ids:
|
||||
- railiance-enablement.delivery-templates.workflow-template-contract
|
||||
criticality: medium
|
||||
data_classification: internal
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: CapabilityDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.artifact-promotion-evidence
|
||||
name: Artifact promotion evidence
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Observability and evidence contract
|
||||
path: /home/worsch/railiance-forge/docs/observability-operating-evidence.md
|
||||
- label: Backup and restore handoff
|
||||
path: /home/worsch/railiance-forge/docs/backup-restore-secret-handoff.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Provides artifact identity, provenance, publish, restore, and release-readiness evidence that downstream releases can cite.
|
||||
capability_type: artifact-promotion-evidence
|
||||
service_id: railiance-forge.source-forge
|
||||
interface_ids:
|
||||
- railiance-forge.source-forge.evidence-contract
|
||||
criticality: high
|
||||
data_classification: internal
|
||||
21
fabric/capabilities/railiance-forge-container-registry.yaml
Normal file
21
fabric/capabilities/railiance-forge-container-registry.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: CapabilityDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.container-registry
|
||||
name: Container registry
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Container registry docs
|
||||
path: /home/worsch/railiance-forge/docs/gitea-container-registry.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Provides the Gitea OCI container registry endpoint used by Railiance workloads.
|
||||
capability_type: container-registry
|
||||
service_id: railiance-forge.source-forge
|
||||
interface_ids:
|
||||
- railiance-forge.source-forge.oci-registry
|
||||
criticality: high
|
||||
data_classification: confidential
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: CapabilityDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.python-package-registry
|
||||
name: Python package registry
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Package registry docs
|
||||
path: /home/worsch/railiance-forge/docs/gitea-package-registry.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Provides the Gitea Python package registry endpoint used by Railiance source and app builds.
|
||||
capability_type: python-package-registry
|
||||
service_id: railiance-forge.source-forge
|
||||
interface_ids:
|
||||
- railiance-forge.source-forge.python-package-index
|
||||
criticality: high
|
||||
data_classification: confidential
|
||||
22
fabric/capabilities/railiance-forge-source-hosting.yaml
Normal file
22
fabric/capabilities/railiance-forge-source-hosting.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: CapabilityDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.source-hosting
|
||||
name: Source hosting
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Forge scope
|
||||
path: /home/worsch/railiance-forge/SCOPE.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Hosts Railiance Git repositories, review surfaces, repository metadata, and source-forge access paths.
|
||||
capability_type: source-hosting
|
||||
service_id: railiance-forge.source-forge
|
||||
interface_ids:
|
||||
- railiance-forge.source-forge.web-ui
|
||||
- railiance-forge.source-forge.git-ssh
|
||||
criticality: high
|
||||
data_classification: confidential
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: CapabilityDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.workflow-runner-substrate
|
||||
name: Workflow runner substrate
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Runner ownership contract
|
||||
path: /home/worsch/railiance-forge/docs/ci-runner-actions-gitops-ownership.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
description: Provides forge-backed runner labels, placement, credential boundaries, and runner health evidence consumed by workflow templates and release checks.
|
||||
capability_type: workflow-runner-substrate
|
||||
service_id: railiance-forge.source-forge
|
||||
interface_ids:
|
||||
- railiance-forge.source-forge.runner-label-contract
|
||||
criticality: high
|
||||
data_classification: restricted
|
||||
30
fabric/dependencies/railiance-apps-artifact-evidence.yaml
Normal file
30
fabric/dependencies/railiance-apps-artifact-evidence.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DependencyDeclaration
|
||||
metadata:
|
||||
id: railiance-apps.s5-releases.needs-artifact-evidence
|
||||
name: S5 artifact evidence dependency
|
||||
owner: railiance-apps
|
||||
repo: railiance-apps
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Apps scope
|
||||
path: /home/worsch/railiance-apps/SCOPE.md
|
||||
- label: Observability and evidence contract
|
||||
path: /home/worsch/railiance-forge/docs/observability-operating-evidence.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
consumer_service_id: railiance-apps.s5-releases
|
||||
requires:
|
||||
capability_type: artifact-promotion-evidence
|
||||
capability_id: railiance-forge.source-forge.artifact-promotion-evidence
|
||||
interface:
|
||||
type: evidence-contract
|
||||
version_constraint: ">=v1"
|
||||
auth:
|
||||
method: none
|
||||
criticality: high
|
||||
data_classification: internal
|
||||
fallback:
|
||||
mode: manual
|
||||
description: App operators can record manual evidence, but S5 should cite forge-owned artifact readiness when promoting releases.
|
||||
30
fabric/dependencies/railiance-apps-container-registry.yaml
Normal file
30
fabric/dependencies/railiance-apps-container-registry.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DependencyDeclaration
|
||||
metadata:
|
||||
id: railiance-apps.s5-releases.needs-container-registry
|
||||
name: S5 container registry dependency
|
||||
owner: railiance-apps
|
||||
repo: railiance-apps
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Apps scope
|
||||
path: /home/worsch/railiance-apps/SCOPE.md
|
||||
- label: Container registry docs
|
||||
path: /home/worsch/railiance-forge/docs/gitea-container-registry.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
consumer_service_id: railiance-apps.s5-releases
|
||||
requires:
|
||||
capability_type: container-registry
|
||||
capability_id: railiance-forge.source-forge.container-registry
|
||||
interface:
|
||||
type: oci-registry
|
||||
version_constraint: ">=registry-v2"
|
||||
auth:
|
||||
method: api_key
|
||||
criticality: high
|
||||
data_classification: confidential
|
||||
fallback:
|
||||
mode: none
|
||||
description: S5 releases require a reachable container registry for private or internal app images.
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DependencyDeclaration
|
||||
metadata:
|
||||
id: railiance-enablement.delivery-templates.needs-runner-substrate
|
||||
name: Enablement runner substrate dependency
|
||||
owner: railiance-enablement
|
||||
repo: railiance-enablement
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Enablement scope
|
||||
path: /home/worsch/railiance-enablement/SCOPE.md
|
||||
- label: Runner ownership contract
|
||||
path: /home/worsch/railiance-forge/docs/ci-runner-actions-gitops-ownership.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
consumer_service_id: railiance-enablement.delivery-templates
|
||||
requires:
|
||||
capability_type: workflow-runner-substrate
|
||||
capability_id: railiance-forge.source-forge.workflow-runner-substrate
|
||||
interface:
|
||||
type: workflow-runner-label-contract
|
||||
version_constraint: ">=v1"
|
||||
auth:
|
||||
method: none
|
||||
criticality: high
|
||||
data_classification: internal
|
||||
fallback:
|
||||
mode: manual
|
||||
description: Reusable templates can remain draft-only until forge publishes runner labels and trust evidence.
|
||||
28
fabric/dependencies/railiance-forge-kubernetes-runtime.yaml
Normal file
28
fabric/dependencies/railiance-forge-kubernetes-runtime.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DependencyDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.needs-kubernetes-runtime
|
||||
name: Forge Kubernetes runtime dependency
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Forge scope
|
||||
path: /home/worsch/railiance-forge/SCOPE.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
consumer_service_id: railiance-forge.source-forge
|
||||
requires:
|
||||
capability_type: kubernetes-runtime
|
||||
capability_id: railiance-cluster.kubernetes.runtime
|
||||
interface:
|
||||
type: kubernetes-api
|
||||
version_constraint: ">=v1"
|
||||
auth:
|
||||
method: kubernetes_service_account
|
||||
criticality: critical
|
||||
data_classification: restricted
|
||||
fallback:
|
||||
mode: none
|
||||
description: The forge runtime cannot operate without the Railiance Kubernetes runtime.
|
||||
30
fabric/dependencies/railiance-forge-object-storage.yaml
Normal file
30
fabric/dependencies/railiance-forge-object-storage.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DependencyDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.needs-object-storage
|
||||
name: Forge object storage dependency
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Backup and restore handoff
|
||||
path: /home/worsch/railiance-forge/docs/backup-restore-secret-handoff.md
|
||||
- label: Platform OpenBao object-storage handoff
|
||||
path: /home/worsch/railiance-platform/docs/openbao.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
consumer_service_id: railiance-forge.source-forge
|
||||
requires:
|
||||
capability_type: object-storage
|
||||
capability_id: artifact-store.object-storage
|
||||
interface:
|
||||
type: object-storage-bucket
|
||||
version_constraint: ">=v1"
|
||||
auth:
|
||||
method: sts_token
|
||||
criticality: high
|
||||
data_classification: confidential
|
||||
fallback:
|
||||
mode: manual
|
||||
description: Current Gitea package blobs remain on PVC until durable object-storage backup or artifact preservation is proven.
|
||||
28
fabric/dependencies/railiance-forge-postgresql.yaml
Normal file
28
fabric/dependencies/railiance-forge-postgresql.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DependencyDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.needs-postgresql
|
||||
name: Forge PostgreSQL dependency
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Backup and restore handoff
|
||||
path: /home/worsch/railiance-forge/docs/backup-restore-secret-handoff.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
consumer_service_id: railiance-forge.source-forge
|
||||
requires:
|
||||
capability_type: postgresql-database-service
|
||||
capability_id: railiance-platform.cnpg.postgresql
|
||||
interface:
|
||||
type: database-connection
|
||||
version_constraint: ">=v16"
|
||||
auth:
|
||||
method: database_role
|
||||
criticality: critical
|
||||
data_classification: confidential
|
||||
fallback:
|
||||
mode: none
|
||||
description: The forge runtime requires the Gitea database state and cannot degrade safely without it.
|
||||
28
fabric/dependencies/railiance-forge-runtime-secrets.yaml
Normal file
28
fabric/dependencies/railiance-forge-runtime-secrets.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DependencyDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.needs-runtime-secrets
|
||||
name: Forge runtime secrets dependency
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Backup and restore handoff
|
||||
path: /home/worsch/railiance-forge/docs/backup-restore-secret-handoff.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
consumer_service_id: railiance-forge.source-forge
|
||||
requires:
|
||||
capability_type: runtime-secrets
|
||||
capability_id: railiance-platform.openbao.runtime-secrets
|
||||
interface:
|
||||
type: openbao-kv-v2-mount
|
||||
version_constraint: ">=v1 <v2"
|
||||
auth:
|
||||
method: kubernetes_service_account
|
||||
criticality: critical
|
||||
data_classification: secret
|
||||
fallback:
|
||||
mode: manual
|
||||
description: SOPS/age bootstrap can carry encrypted deploy input, but runtime secret custody belongs to the platform path.
|
||||
227
fabric/discovery/railiance-accountability-roots.yaml
Normal file
227
fabric/discovery/railiance-accountability-roots.yaml
Normal file
@@ -0,0 +1,227 @@
|
||||
apiVersion: railiance.fabric/v1alpha2
|
||||
kind: AccountabilityRootManifest
|
||||
metadata:
|
||||
id: railiance.accountability-roots
|
||||
name: Railiance Accountability Roots
|
||||
description: Current discovery roots for rebuilding the Railiance Fabric graph from durable accountability evidence.
|
||||
source_links:
|
||||
- label: Financial Fabric architecture
|
||||
path: docs/FabricDiscoveryAndUpdate.md
|
||||
- label: Financial baseline
|
||||
path: fabric/financial/railiance-netkingdom.yaml
|
||||
netkingdom:
|
||||
id: railiance.netkingdom
|
||||
name: Railiance Netkingdom
|
||||
king_actor_id: actor.railiance.king
|
||||
baseline_ref:
|
||||
label: Railiance financial baseline
|
||||
path: fabric/financial/railiance-netkingdom.yaml
|
||||
actors:
|
||||
- id: actor.railiance.king
|
||||
role: king
|
||||
name: Railiance King
|
||||
description: Responsible for the Railiance netkingdom and recovery authority.
|
||||
authority:
|
||||
recovery_authority: true
|
||||
secrets_authority: true
|
||||
backup_authority: true
|
||||
termination_authority: true
|
||||
- id: actor.railiance.primary-lord
|
||||
role: lord
|
||||
name: Railiance Primary Lord
|
||||
description: Pays for the current Railiance infrastructure boundary.
|
||||
fabrics:
|
||||
- id: fabric.railiance.primary
|
||||
kind: Fabric
|
||||
name: Railiance Primary Fabric
|
||||
netkingdom_id: railiance.netkingdom
|
||||
lord_actor_id: actor.railiance.primary-lord
|
||||
parent_fabric_id: null
|
||||
status: active
|
||||
boundary:
|
||||
boundary_type: fabric
|
||||
criterion: financial_and_operational_accountability
|
||||
payment_responsibility: actor.railiance.primary-lord
|
||||
operational_responsibility: actor.railiance.king
|
||||
recovery_responsibility: actor.railiance.king
|
||||
evidence_refs:
|
||||
- label: Railiance financial baseline
|
||||
path: fabric/financial/railiance-netkingdom.yaml
|
||||
discovery_roots:
|
||||
- id: root.state-hub.attached-repos
|
||||
type: state_hub_repo_inventory
|
||||
status: active
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
source:
|
||||
base_url: http://127.0.0.1:8000
|
||||
api_paths:
|
||||
- /managed-repos/
|
||||
safe_discovery: metadata_only
|
||||
evidence_scope:
|
||||
- repo_inventory
|
||||
- repository_identity
|
||||
refresh:
|
||||
cadence: on_change
|
||||
triggers:
|
||||
- state_hub_repo_inventory_change
|
||||
- operator_request
|
||||
notes: Read State Hub as repo inventory evidence only; State Hub does not author Fabric ownership or topology.
|
||||
- id: root.gitea.coulomb
|
||||
type: gitea_organization
|
||||
status: active
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
source:
|
||||
url: ssh://git@92.205.130.254:30022/coulomb
|
||||
organization: coulomb
|
||||
safe_discovery: metadata_only
|
||||
evidence_scope:
|
||||
- repo_inventory
|
||||
- repository_identity
|
||||
refresh:
|
||||
cadence: on_change
|
||||
triggers:
|
||||
- git_commit
|
||||
- operator_request
|
||||
- id: root.registry.local-repos
|
||||
type: registry_manifest
|
||||
status: active
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
source:
|
||||
manifest_path: registry/local-repos.yaml
|
||||
safe_discovery: local_files
|
||||
evidence_scope:
|
||||
- repo_inventory
|
||||
- repository_identity
|
||||
- local_checkout
|
||||
refresh:
|
||||
cadence: on_change
|
||||
triggers:
|
||||
- state_hub_repo_inventory_change
|
||||
- operator_request
|
||||
- id: root.workspace.home-worsch
|
||||
type: host_path
|
||||
status: active
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
source:
|
||||
path: /home/worsch
|
||||
patterns:
|
||||
- "*/.git"
|
||||
- "*/fabric"
|
||||
safe_discovery: local_files
|
||||
evidence_scope:
|
||||
- local_checkout
|
||||
- repository_identity
|
||||
refresh:
|
||||
cadence: manual
|
||||
triggers:
|
||||
- operator_request
|
||||
- id: root.railiance-fabric.checkout
|
||||
type: repository_checkout
|
||||
status: active
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
source:
|
||||
repo_slug: railiance-fabric
|
||||
path: /home/worsch/railiance-fabric
|
||||
remote_url: gitea-remote:coulomb/railiance-fabric.git
|
||||
safe_discovery: local_files
|
||||
evidence_scope:
|
||||
- repository_identity
|
||||
- local_checkout
|
||||
- service_configuration
|
||||
- endpoint_contract
|
||||
- deployment_topology
|
||||
refresh:
|
||||
cadence: on_change
|
||||
triggers:
|
||||
- git_commit
|
||||
- deployment_manifest_change
|
||||
- endpoint_contract_change
|
||||
- operator_request
|
||||
- id: root.deployment.local-manifests
|
||||
type: deployment_automation
|
||||
status: active
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
source:
|
||||
path: /home/worsch
|
||||
patterns:
|
||||
- "**/compose.yaml"
|
||||
- "**/compose.yml"
|
||||
- "**/docker-compose.yaml"
|
||||
- "**/Dockerfile"
|
||||
- "**/*.service"
|
||||
- "**/k8s/*.yaml"
|
||||
- "**/deploy*.sh"
|
||||
safe_discovery: local_files
|
||||
evidence_scope:
|
||||
- deployment_topology
|
||||
- infrastructure
|
||||
- service_configuration
|
||||
refresh:
|
||||
cadence: on_change
|
||||
triggers:
|
||||
- deployment_manifest_change
|
||||
- infrastructure_manifest_change
|
||||
- operator_request
|
||||
- id: root.openbao.secret-metadata
|
||||
type: secret_root
|
||||
status: planned
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.king
|
||||
source:
|
||||
repo_slug: railiance-fabric
|
||||
path: fabric/services/railiance-platform-openbao.yaml
|
||||
safe_discovery: metadata_only
|
||||
evidence_scope:
|
||||
- secret_metadata
|
||||
- infrastructure
|
||||
refresh:
|
||||
cadence: manual
|
||||
triggers:
|
||||
- secret_root_change
|
||||
- operator_request
|
||||
notes: Discover only existence and metadata for secret roots; never extract secret values.
|
||||
- id: root.backup-recovery.metadata
|
||||
type: backup_recovery
|
||||
status: planned
|
||||
fabric_id: fabric.railiance.primary
|
||||
owner_actor_id: actor.railiance.king
|
||||
source:
|
||||
path: docs/financial-fabric-operator-guide.md
|
||||
safe_discovery: explicit_review
|
||||
evidence_scope:
|
||||
- backup_recovery
|
||||
- manual_review
|
||||
refresh:
|
||||
cadence: manual
|
||||
triggers:
|
||||
- backup_recovery_change
|
||||
- operator_request
|
||||
refresh:
|
||||
cadence: manual
|
||||
triggers:
|
||||
- operator_request
|
||||
- state_hub_repo_inventory_change
|
||||
- git_commit
|
||||
- deployment_manifest_change
|
||||
- infrastructure_manifest_change
|
||||
- endpoint_contract_change
|
||||
- secret_root_change
|
||||
- backup_recovery_change
|
||||
- lord_or_tenant_change
|
||||
notes: Manual rebuild is the default until snapshot deltas and freshness triggers are implemented.
|
||||
templates:
|
||||
future_subfabric:
|
||||
parent_fabric_id: fabric.railiance.primary
|
||||
tenant_actor_role: tenant
|
||||
required_updates:
|
||||
- Add tenant actor with role tenant.
|
||||
- Add Subfabric under fabric.railiance.primary with tenant_actor_id.
|
||||
- Add tenant-specific discovery roots with subfabric_id.
|
||||
- Add cross-boundary utility edges with provider and consumer owner context.
|
||||
note: Tenant subfabrics do not change the current root fabric criterion.
|
||||
@@ -0,0 +1,404 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: DeploymentZoneInventory
|
||||
generated_at: "2026-05-24T00:00:00+02:00"
|
||||
source:
|
||||
repo: railiance-fabric
|
||||
workplan: RAIL-FAB-WP-0020
|
||||
method: source-search-and-declared-surfaces
|
||||
scope:
|
||||
note: >
|
||||
This inventory captures deployment-zone overlay evidence. It does not
|
||||
define fabric membership, port ownership, live health, or access policy.
|
||||
deployment_environments:
|
||||
- id: dev
|
||||
scenario: bernd-laptop
|
||||
intended_reachability: private operator workstation
|
||||
- id: test
|
||||
scenario: coulombcore
|
||||
intended_reachability: shared collaborator and early-access test stage
|
||||
- id: prod
|
||||
scenario: railiance01
|
||||
intended_reachability: production stage, currently alpha-accessible to developers
|
||||
surfaces:
|
||||
- id: dev.bernd-laptop.railiance-fabric.registry-api
|
||||
name: Railiance Fabric registry HTTP API
|
||||
repo: railiance-fabric
|
||||
service_id: railiance-fabric.registry
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
access_zone: private-dev
|
||||
exposure_class: local-only
|
||||
routing_authority: local-loopback-binding
|
||||
policy_authority: local-loopback-binding
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:8765
|
||||
host: 127.0.0.1
|
||||
port: 8765
|
||||
protocol: http
|
||||
evidence:
|
||||
- path: fabric/interfaces/railiance-fabric-registry-http-api.yaml
|
||||
kind: fabric-interface-declaration
|
||||
- id: dev.bernd-laptop.railiance-fabric.graph-explorer
|
||||
name: Railiance Fabric graph explorer UI
|
||||
repo: railiance-fabric
|
||||
service_id: railiance-fabric.registry
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
access_zone: private-dev
|
||||
exposure_class: local-only
|
||||
routing_authority: local-loopback-binding
|
||||
policy_authority: local-loopback-binding
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:8765/ui/graph-explorer
|
||||
host: 127.0.0.1
|
||||
port: 8765
|
||||
protocol: http
|
||||
path: /ui/graph-explorer
|
||||
evidence:
|
||||
- path: fabric/interfaces/railiance-fabric-registry-graph-explorer-ui.yaml
|
||||
kind: fabric-interface-declaration
|
||||
- id: dev.bernd-laptop.state-hub.api
|
||||
name: State Hub HTTP API
|
||||
repo: the-custodian
|
||||
service_id: the-custodian.state-hub
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
access_zone: private-dev
|
||||
exposure_class: local-only
|
||||
routing_authority: local-loopback-binding
|
||||
policy_authority: local-loopback-binding
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:8000
|
||||
host: 127.0.0.1
|
||||
port: 8000
|
||||
protocol: http
|
||||
evidence:
|
||||
- path: fabric/interfaces/the-custodian-state-hub-http-api.yaml
|
||||
kind: fabric-interface-declaration
|
||||
- id: dev.bernd-laptop.state-hub.mcp
|
||||
name: State Hub MCP API
|
||||
repo: the-custodian
|
||||
service_id: the-custodian.state-hub
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
access_zone: private-dev
|
||||
exposure_class: local-only
|
||||
routing_authority: local-loopback-binding
|
||||
policy_authority: local-loopback-binding
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:8001
|
||||
host: 127.0.0.1
|
||||
port: 8001
|
||||
protocol: http
|
||||
evidence:
|
||||
- path: fabric/interfaces/the-custodian-state-hub-mcp-api.yaml
|
||||
kind: fabric-interface-declaration
|
||||
- id: dev.bernd-laptop.state-hub.dashboard
|
||||
name: State Hub dashboard
|
||||
repo: the-custodian
|
||||
service_id: the-custodian.state-hub
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
access_zone: private-dev
|
||||
exposure_class: local-only
|
||||
routing_authority: local-loopback-binding
|
||||
policy_authority: local-loopback-binding
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:3000
|
||||
host: 127.0.0.1
|
||||
port: 3000
|
||||
protocol: http
|
||||
evidence:
|
||||
- path: fabric/interfaces/the-custodian-state-hub-dashboard.yaml
|
||||
kind: fabric-interface-declaration
|
||||
- id: dev.bernd-laptop.net-kingdom.control-surface
|
||||
name: NetKingdom control surface
|
||||
repo: net-kingdom
|
||||
service_id: net-kingdom.iam-profile
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
access_zone: private-dev
|
||||
exposure_class: local-only
|
||||
routing_authority: local-loopback-binding
|
||||
policy_authority: local-loopback-binding
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:8876
|
||||
host: 127.0.0.1
|
||||
port: 8876
|
||||
protocol: http
|
||||
evidence:
|
||||
- path: fabric/interfaces/net-kingdom-control-surface-ui.yaml
|
||||
kind: fabric-interface-declaration
|
||||
- path: ../net-kingdom/sso-mfa/k8s/keycape/README.md
|
||||
kind: source-search-hit
|
||||
note: local OIDC callback lists localhost port 8876
|
||||
- id: test.coulombcore.state-hub.http-tunnel
|
||||
name: State Hub HTTP API tunnel to coulombcore
|
||||
repo: railiance-infra
|
||||
service_id: the-custodian.state-hub
|
||||
deployment_environment: test
|
||||
deployment_scenario: coulombcore
|
||||
access_zone: collaborator-test
|
||||
exposure_class: collaborator-test
|
||||
routing_authority: ops-bridge
|
||||
policy_authority: ops-bridge-ssh
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:18000
|
||||
host: 127.0.0.1
|
||||
port: 18000
|
||||
protocol: http
|
||||
tunnel_target: coulombcore
|
||||
evidence:
|
||||
- path: ../railiance-infra/docs/deploy-stack.md
|
||||
lines: "127"
|
||||
kind: source-search-hit
|
||||
- id: test.coulombcore.state-hub.mcp-tunnel
|
||||
name: State Hub MCP tunnel to coulombcore
|
||||
repo: railiance-infra
|
||||
service_id: the-custodian.state-hub
|
||||
deployment_environment: test
|
||||
deployment_scenario: coulombcore
|
||||
access_zone: collaborator-test
|
||||
exposure_class: collaborator-test
|
||||
routing_authority: ops-bridge
|
||||
policy_authority: ops-bridge-ssh
|
||||
route_evidence:
|
||||
route: http://127.0.0.1:18001
|
||||
host: 127.0.0.1
|
||||
port: 18001
|
||||
protocol: http
|
||||
tunnel_target: coulombcore
|
||||
evidence:
|
||||
- path: ../railiance-infra/docs/deploy-stack.md
|
||||
lines: "128"
|
||||
kind: source-search-hit
|
||||
- id: test.coulombcore.k3s-api-tunnel
|
||||
name: k3s API tunnel to coulombcore
|
||||
repo: railiance-infra
|
||||
deployment_environment: test
|
||||
deployment_scenario: coulombcore
|
||||
access_zone: collaborator-test
|
||||
exposure_class: collaborator-test
|
||||
routing_authority: ops-bridge
|
||||
policy_authority: ops-bridge-ssh
|
||||
route_evidence:
|
||||
route: https://127.0.0.1:16443
|
||||
host: 127.0.0.1
|
||||
port: 16443
|
||||
protocol: https
|
||||
tunnel_target: coulombcore
|
||||
evidence:
|
||||
- path: ../railiance-infra/docs/deploy-stack.md
|
||||
lines: "129"
|
||||
kind: source-search-hit
|
||||
- path: ../railiance-cluster/SCOPE.md
|
||||
lines: "127"
|
||||
kind: source-search-hit
|
||||
note: cluster scope states it runs on COULOMBCORE
|
||||
- id: prod.railiance01.gitea
|
||||
name: Gitea ingress
|
||||
repo: railiance-apps
|
||||
deployment_environment: prod
|
||||
deployment_scenario: railiance01
|
||||
access_zone: production-public
|
||||
exposure_class: production-public
|
||||
routing_authority: traefik
|
||||
policy_authority: null
|
||||
tls_authority: cert-manager:letsencrypt-prod
|
||||
route_evidence:
|
||||
route: https://gitea.coulomb.social
|
||||
hostname: gitea.coulomb.social
|
||||
port: 443
|
||||
protocol: https
|
||||
review:
|
||||
status: candidate
|
||||
note: access zone and policy authority require operator review
|
||||
evidence:
|
||||
- path: ../railiance-apps/manifests/gitea-ingress.yaml
|
||||
lines: "2,12,14,16,27"
|
||||
kind: kubernetes-ingress
|
||||
- path: ../railiance-apps/workplans/railiance-apps-WP-0002-vergabe-teilnahme-on-railiance01.md
|
||||
lines: "612,613"
|
||||
kind: source-search-hit
|
||||
note: places Gitea before vergabe-teilnahme on railiance01
|
||||
- id: prod.railiance01.vergabe-teilnahme
|
||||
name: Vergabe Teilnahme ingress
|
||||
repo: railiance-apps
|
||||
deployment_environment: prod
|
||||
deployment_scenario: railiance01
|
||||
access_zone: production-public
|
||||
exposure_class: production-public
|
||||
routing_authority: traefik
|
||||
policy_authority: null
|
||||
tls_authority: cert-manager:letsencrypt-prod
|
||||
route_evidence:
|
||||
route: https://vergabe-teilnahme.whywhynot.de
|
||||
hostname: vergabe-teilnahme.whywhynot.de
|
||||
port: 443
|
||||
protocol: https
|
||||
review:
|
||||
status: candidate
|
||||
note: production public classification is inferred from ingress host and workplan
|
||||
evidence:
|
||||
- path: ../railiance-apps/manifests/vergabe-teilnahme-ingress.yaml
|
||||
lines: "2,11,13,15,26"
|
||||
kind: kubernetes-ingress
|
||||
- path: ../railiance-apps/workplans/railiance-apps-WP-0002-vergabe-teilnahme-on-railiance01.md
|
||||
lines: "22,40,68,69,163,612,613"
|
||||
kind: source-search-hit
|
||||
- id: prod.railiance01.authelia
|
||||
name: Authelia ingress
|
||||
repo: net-kingdom
|
||||
deployment_environment: prod
|
||||
deployment_scenario: railiance01
|
||||
access_zone: production-public
|
||||
exposure_class: production-public
|
||||
routing_authority: traefik
|
||||
policy_authority: authelia
|
||||
tls_authority: cert-manager:letsencrypt-prod
|
||||
route_evidence:
|
||||
route: https://auth.coulomb.social
|
||||
hostname: auth.coulomb.social
|
||||
port: 443
|
||||
protocol: https
|
||||
review:
|
||||
status: candidate
|
||||
note: railiance01 attribution comes from NetKingdom deployment workplan
|
||||
evidence:
|
||||
- path: ../net-kingdom/sso-mfa/k8s/authelia/ingress.yaml
|
||||
lines: "13,22,24,26,38"
|
||||
kind: kubernetes-ingress
|
||||
- path: ../net-kingdom/workplans/NK-WP-0003-keycape-privacyidea-cluster-deployment.md
|
||||
lines: "29,47,88,101"
|
||||
kind: source-search-hit
|
||||
- id: prod.railiance01.keycape
|
||||
name: Keycape ingress
|
||||
repo: net-kingdom
|
||||
deployment_environment: prod
|
||||
deployment_scenario: railiance01
|
||||
access_zone: production-public
|
||||
exposure_class: production-public
|
||||
routing_authority: traefik
|
||||
policy_authority: traefik-middleware
|
||||
tls_authority: cert-manager:letsencrypt-prod
|
||||
route_evidence:
|
||||
route: https://kc.coulomb.social
|
||||
hostname: kc.coulomb.social
|
||||
port: 443
|
||||
protocol: https
|
||||
review:
|
||||
status: candidate
|
||||
note: middleware is present, but intended audience still needs operator review
|
||||
evidence:
|
||||
- path: ../net-kingdom/sso-mfa/k8s/keycape/ingress.yaml
|
||||
lines: "13,22,23,27,29,41"
|
||||
kind: kubernetes-ingress
|
||||
- path: ../net-kingdom/sso-mfa/k8s/keycape/middleware.yaml
|
||||
lines: "9,24"
|
||||
kind: traefik-middleware
|
||||
- id: prod.railiance01.privacyidea
|
||||
name: privacyIDEA ingress
|
||||
repo: net-kingdom
|
||||
deployment_environment: prod
|
||||
deployment_scenario: railiance01
|
||||
access_zone: production-admin
|
||||
exposure_class: production-admin
|
||||
routing_authority: traefik
|
||||
policy_authority: traefik-middleware
|
||||
tls_authority: cert-manager:letsencrypt-prod
|
||||
route_evidence:
|
||||
route: https://pink.coulomb.social
|
||||
hostname: pink.coulomb.social
|
||||
port: 443
|
||||
protocol: https
|
||||
review:
|
||||
status: candidate
|
||||
note: admin classification inferred from privacyIDEA role and middleware
|
||||
evidence:
|
||||
- path: ../net-kingdom/sso-mfa/k8s/privacyidea/ingress.yaml
|
||||
lines: "25,34,36,38,40,52,60,69,71,75,77,89"
|
||||
kind: kubernetes-ingress
|
||||
- path: ../net-kingdom/sso-mfa/k8s/privacyidea/middleware.yaml
|
||||
lines: "19,41"
|
||||
kind: traefik-middleware
|
||||
- id: prod.railiance01.privacyidea-account
|
||||
name: privacyIDEA account self-service ingress
|
||||
repo: net-kingdom
|
||||
deployment_environment: prod
|
||||
deployment_scenario: railiance01
|
||||
access_zone: production-public
|
||||
exposure_class: production-public
|
||||
routing_authority: traefik
|
||||
policy_authority: traefik-middleware
|
||||
tls_authority: cert-manager:letsencrypt-prod
|
||||
route_evidence:
|
||||
route: https://pink-account.coulomb.social
|
||||
hostname: pink-account.coulomb.social
|
||||
port: 443
|
||||
protocol: https
|
||||
review:
|
||||
status: candidate
|
||||
note: self-service classification inferred from host name and middleware
|
||||
evidence:
|
||||
- path: ../net-kingdom/sso-mfa/k8s/privacyidea/ingress.yaml
|
||||
lines: "94,103,104,106,108,120"
|
||||
kind: kubernetes-ingress
|
||||
- id: prod.railiance01.lldap
|
||||
name: LLDAP ingress
|
||||
repo: net-kingdom
|
||||
deployment_environment: prod
|
||||
deployment_scenario: railiance01
|
||||
access_zone: production-admin
|
||||
exposure_class: production-admin
|
||||
routing_authority: traefik
|
||||
policy_authority: traefik-admin-allowlist
|
||||
tls_authority: cert-manager:letsencrypt-prod
|
||||
route_evidence:
|
||||
route: https://lldap.coulomb.social
|
||||
hostname: lldap.coulomb.social
|
||||
port: 443
|
||||
protocol: https
|
||||
review:
|
||||
status: candidate
|
||||
note: admin allowlist middleware indicates intended restricted access
|
||||
evidence:
|
||||
- path: ../net-kingdom/sso-mfa/k8s/lldap/ingress.yaml
|
||||
lines: "12,21,22,24,26,38"
|
||||
kind: kubernetes-ingress
|
||||
- path: ../net-kingdom/sso-mfa/k8s/lldap/middleware.yaml
|
||||
lines: "11"
|
||||
kind: traefik-middleware
|
||||
ambiguities:
|
||||
- id: railiance01-coulombcore-ip-conflict
|
||||
severity: high
|
||||
summary: Source documents disagree on which host owns 92.205.130.254.
|
||||
evidence:
|
||||
- path: ../railiance-apps/workplans/railiance-apps-WP-0002-vergabe-teilnahme-on-railiance01.md
|
||||
lines: "22,163"
|
||||
note: says railiance01 and Traefik LoadBalancer use 92.205.130.254
|
||||
- path: ../railiance-infra/SCOPE.md
|
||||
lines: "126"
|
||||
note: says COULOMBCORE is 92.205.130.254 and Railiance01 is 92.205.62.239
|
||||
next: reconcile host inventory before treating IP evidence as authoritative
|
||||
- id: prod-access-zone-review
|
||||
severity: medium
|
||||
summary: Production access zones are candidate classifications.
|
||||
evidence:
|
||||
- path: ../railiance-apps/manifests
|
||||
note: app ingress manifests show routing and TLS but not business audience
|
||||
- path: ../net-kingdom/sso-mfa/k8s
|
||||
note: middleware and network policy hint at access intent but do not replace operator review
|
||||
next: confirm each production host as public, admin, or early-access
|
||||
- id: test-reachability-is-tunneled
|
||||
severity: medium
|
||||
summary: Current coulombcore routes are ops-bridge tunnel evidence, not public ingress evidence.
|
||||
evidence:
|
||||
- path: ../railiance-infra/docs/deploy-stack.md
|
||||
lines: "127,128,129"
|
||||
note: state-hub and k3s API access are tunnel commands
|
||||
next: add executable test-stage ingress/service discovery when coulombcore manifests exist
|
||||
missing_policy_authority:
|
||||
- surface_id: prod.railiance01.gitea
|
||||
reason: route and TLS are discovered, but access policy authority is not evident in the ingress artifact
|
||||
- surface_id: prod.railiance01.vergabe-teilnahme
|
||||
reason: route and TLS are discovered, but access policy authority is not evident in the ingress artifact
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"apiVersion": "railiance.fabric/v1alpha2",
|
||||
"change_sets": {
|
||||
"blockers": [],
|
||||
"containment": [],
|
||||
"ownership": [],
|
||||
"review_state": []
|
||||
},
|
||||
"current": {
|
||||
"generated_at": "2026-05-24T12:04:20Z",
|
||||
"manifest_fingerprint": "dd279b655d68222a99671c1c875afdaa70ae2f907fd526e6673ddf756bc90dae",
|
||||
"manifest_id": "railiance.accountability-roots"
|
||||
},
|
||||
"edge_delta": {
|
||||
"added": [
|
||||
"candidate-edge:d448183c64bba5c0"
|
||||
],
|
||||
"changed": [],
|
||||
"removed": [],
|
||||
"unchanged": []
|
||||
},
|
||||
"generated_at": "2026-05-24T12:04:59Z",
|
||||
"kind": "AccountabilityUpdateDelta",
|
||||
"node_delta": {
|
||||
"added": [
|
||||
"identity:actor:actor.railiance.king",
|
||||
"identity:actor:actor.railiance.primary-lord",
|
||||
"identity:backup-recovery-root:docs-financial-fabric-operator-guide.md",
|
||||
"identity:catalog-root:gitea_organization",
|
||||
"identity:catalog-root:registry_manifest",
|
||||
"identity:deployable:home-worsch-.config-systemd-user-custodian-sync.service",
|
||||
"identity:deployable:home-worsch-activity-core-dockerfile",
|
||||
"identity:deployable:home-worsch-go-src-crypto-internal-boring-dockerfile",
|
||||
"identity:deployable:home-worsch-go-src-crypto-internal-nistec-fiat-dockerfile",
|
||||
"identity:deployable:home-worsch-key-cape-dockerfile",
|
||||
"identity:deployable:home-worsch-repo-scoping-var-checkouts-key-cape-a01db9828dd4-dockerfile",
|
||||
"identity:deployable:home-worsch-state-hub-dockerfile",
|
||||
"identity:deployable:home-worsch-the-custodian-infra-build-machines-haskell-files-build-agent.service",
|
||||
"identity:deployable:home-worsch-vergabe-teilnahme-dockerfile",
|
||||
"identity:fabric:fabric.railiance.primary",
|
||||
"identity:host-path:fabric",
|
||||
"identity:host-path:git",
|
||||
"identity:host-path:home-worsch-.nvm-.git",
|
||||
"identity:host-path:home-worsch-activity-core-.git",
|
||||
"identity:host-path:home-worsch-artifact-store-.git",
|
||||
"identity:host-path:home-worsch-can-you-assist-.git",
|
||||
"identity:host-path:home-worsch-domain-tree-.git",
|
||||
"identity:host-path:home-worsch-flex-auth-.git",
|
||||
"identity:host-path:home-worsch-guide-board-.git",
|
||||
"identity:host-path:home-worsch-helix-forge-.git",
|
||||
"identity:host-path:home-worsch-ihp-railiance-probe-.git",
|
||||
"identity:host-path:home-worsch-info-tech-canon-.git",
|
||||
"identity:host-path:home-worsch-infospace-bench-.git",
|
||||
"identity:host-path:home-worsch-inter-hub-.git",
|
||||
"identity:host-path:home-worsch-issue-core-.git",
|
||||
"identity:host-path:home-worsch-key-cape-.git",
|
||||
"identity:host-path:home-worsch-kontextual-engine-.git",
|
||||
"identity:host-path:home-worsch-llm-connect-.git",
|
||||
"identity:host-path:home-worsch-markitect-filter-.git",
|
||||
"identity:host-path:home-worsch-markitect-main-.git",
|
||||
"identity:host-path:home-worsch-markitect-quarkdown-.git",
|
||||
"identity:host-path:home-worsch-markitect-tool-.git",
|
||||
"identity:host-path:home-worsch-net-kingdom-.git",
|
||||
"identity:host-path:home-worsch-open-cmis-tck-.git",
|
||||
"identity:host-path:home-worsch-open-reuse-.git",
|
||||
"identity:host-path:home-worsch-ops-bridge-.git",
|
||||
"identity:host-path:home-worsch-ops-warden-.git",
|
||||
"identity:host-path:home-worsch-phase-memory-.git",
|
||||
"identity:host-path:home-worsch-railiance-apps-.git",
|
||||
"identity:host-path:home-worsch-railiance-cluster-.git",
|
||||
"identity:host-path:home-worsch-railiance-enablement-.git",
|
||||
"identity:host-path:home-worsch-railiance-infra-.git",
|
||||
"identity:host-path:home-worsch-railiance-platform-.git",
|
||||
"identity:host-path:home-worsch-repo-scoping-.git",
|
||||
"identity:host-path:home-worsch-repo-seed-.git",
|
||||
"identity:host-path:home-worsch-shard-wiki-.git",
|
||||
"identity:host-path:home-worsch-state-hub-.git",
|
||||
"identity:host-path:home-worsch-tegwick-control-.git",
|
||||
"identity:host-path:home-worsch-the-custodian-.git",
|
||||
"identity:host-path:home-worsch-user-engine-.git",
|
||||
"identity:host-path:home-worsch-vantage-point-.git",
|
||||
"identity:host-path:home-worsch-vergabe-teilnahme-.git",
|
||||
"identity:host-path:home-worsch-whynot-control-.git",
|
||||
"identity:host-path:home-worsch-whynot-design-.git",
|
||||
"identity:netkingdom:railiance.netkingdom",
|
||||
"identity:repository:activity-core",
|
||||
"identity:repository:artifact-store",
|
||||
"identity:repository:domain-tree",
|
||||
"identity:repository:flex-auth",
|
||||
"identity:repository:guide-board",
|
||||
"identity:repository:helix-forge",
|
||||
"identity:repository:ihp-railiance-probe",
|
||||
"identity:repository:infospace-bench",
|
||||
"identity:repository:inter-hub",
|
||||
"identity:repository:issue-core",
|
||||
"identity:repository:key-cape",
|
||||
"identity:repository:kontextual-engine",
|
||||
"identity:repository:llm-connect",
|
||||
"identity:repository:markitect-filter",
|
||||
"identity:repository:markitect-project",
|
||||
"identity:repository:markitect-quarkdown",
|
||||
"identity:repository:markitect-tool",
|
||||
"identity:repository:net-kingdom",
|
||||
"identity:repository:open-cmis-tck",
|
||||
"identity:repository:open-reuse",
|
||||
"identity:repository:ops-bridge",
|
||||
"identity:repository:ops-warden",
|
||||
"identity:repository:phase-memory",
|
||||
"identity:repository:railiance-apps",
|
||||
"identity:repository:railiance-cluster",
|
||||
"identity:repository:railiance-enablement",
|
||||
"identity:repository:railiance-fabric",
|
||||
"identity:repository:railiance-infra",
|
||||
"identity:repository:railiance-platform",
|
||||
"identity:repository:repo-scoping",
|
||||
"identity:repository:state-hub",
|
||||
"identity:repository:the-custodian",
|
||||
"identity:repository:vergabe-teilnahme",
|
||||
"identity:secret-root:fabric-services-railiance-platform-openbao.yaml"
|
||||
],
|
||||
"changed": [],
|
||||
"removed": [],
|
||||
"unchanged": []
|
||||
},
|
||||
"previous": {},
|
||||
"summary": {
|
||||
"edges_added": 1,
|
||||
"edges_changed": 0,
|
||||
"edges_removed": 0,
|
||||
"edges_unchanged": 0,
|
||||
"meaningful_change_count": 95,
|
||||
"nodes_added": 94,
|
||||
"nodes_changed": 0,
|
||||
"nodes_removed": 0,
|
||||
"nodes_unchanged": 0,
|
||||
"promotion_needed": true
|
||||
}
|
||||
}
|
||||
65
fabric/financial/railiance-netkingdom.yaml
Normal file
65
fabric/financial/railiance-netkingdom.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
apiVersion: railiance.fabric/v1alpha2
|
||||
kind: FinancialFabricBaseline
|
||||
metadata:
|
||||
id: railiance.netkingdom-baseline
|
||||
name: Railiance Netkingdom Baseline
|
||||
spec:
|
||||
netkingdom:
|
||||
id: railiance.netkingdom
|
||||
name: Railiance Netkingdom
|
||||
king_actor_id: actor.railiance.king
|
||||
actors:
|
||||
- id: actor.railiance.king
|
||||
kind: FabricActor
|
||||
role: king
|
||||
name: Railiance King
|
||||
description: Responsible for the Railiance netkingdom and recovery authority.
|
||||
authority:
|
||||
recovery_authority: true
|
||||
secrets_authority: true
|
||||
backup_authority: true
|
||||
termination_authority: true
|
||||
- id: actor.railiance.primary-lord
|
||||
kind: FabricActor
|
||||
role: lord
|
||||
name: Railiance Primary Lord
|
||||
description: Pays for the current Railiance infrastructure boundary.
|
||||
fabrics:
|
||||
- id: fabric.railiance.primary
|
||||
kind: Fabric
|
||||
name: Railiance Primary Fabric
|
||||
netkingdom_id: railiance.netkingdom
|
||||
lord_actor_id: actor.railiance.primary-lord
|
||||
parent_fabric_id: null
|
||||
status: active
|
||||
boundary:
|
||||
boundary_type: fabric
|
||||
criterion: financial_and_operational_accountability
|
||||
payment_responsibility: actor.railiance.primary-lord
|
||||
operational_responsibility: actor.railiance.king
|
||||
recovery_responsibility: actor.railiance.king
|
||||
evidence_refs: []
|
||||
defaults:
|
||||
containment:
|
||||
netkingdom_id: railiance.netkingdom
|
||||
fabric_id: fabric.railiance.primary
|
||||
subfabric_id: null
|
||||
environment: local
|
||||
deployment_scenario_id: null
|
||||
ownership:
|
||||
owner_actor_id: actor.railiance.primary-lord
|
||||
owner_role: lord
|
||||
resolution: inherited
|
||||
inherited_from: fabric.railiance.primary
|
||||
supporting_actor_ids:
|
||||
- actor.railiance.king
|
||||
accounting:
|
||||
cost_center_id: null
|
||||
profit_center_id: null
|
||||
allocation_model: null
|
||||
future_subfabric_template:
|
||||
kind: Subfabric
|
||||
parent_fabric_id: fabric.railiance.primary
|
||||
netkingdom_id: railiance.netkingdom
|
||||
tenant_actor_role: tenant
|
||||
note: Add a tenant actor and subfabric without changing the root fabric criterion.
|
||||
35
fabric/interfaces/net-kingdom-control-surface-ui.yaml
Normal file
35
fabric/interfaces/net-kingdom-control-surface-ui.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: net-kingdom.control-surface.ui
|
||||
name: NetKingdom Control Surface
|
||||
owner: net-kingdom
|
||||
repo: net-kingdom
|
||||
domain: railiance
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev]
|
||||
description: Local NetKingdom control surface on the operator workstation.
|
||||
interface_type: web-ui
|
||||
version: v1
|
||||
service_id: net-kingdom.iam-profile
|
||||
capability_ids:
|
||||
- net-kingdom.iam-profile.issuer
|
||||
endpoint:
|
||||
url: http://127.0.0.1:8876
|
||||
notes: Local workstation endpoint after moving away from the Fabric registry port.
|
||||
deployment_overlay:
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
routing_authority: net-kingdom-local-process
|
||||
access_zone: private-dev
|
||||
policy_authority: local-loopback-binding
|
||||
exposure_class: local-only
|
||||
route_evidence:
|
||||
host: 127.0.0.1
|
||||
port: 8876
|
||||
protocol: tcp
|
||||
route: http://127.0.0.1:8876
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
23
fabric/interfaces/railiance-cluster-kubernetes-api.yaml
Normal file
23
fabric/interfaces/railiance-cluster-kubernetes-api.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-cluster.kubernetes.api
|
||||
name: Kubernetes API
|
||||
owner: railiance-cluster
|
||||
repo: railiance-cluster
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Cluster scope
|
||||
path: /home/worsch/railiance-cluster/SCOPE.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Kubernetes API surface and RBAC-controlled runtime contract consumed by Railiance workloads and operators.
|
||||
interface_type: kubernetes-api
|
||||
version: v1
|
||||
service_id: railiance-cluster.kubernetes
|
||||
capability_ids:
|
||||
- railiance-cluster.kubernetes.runtime
|
||||
auth:
|
||||
method: kubernetes_service_account
|
||||
data_classification: restricted
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-enablement.delivery-templates.workflow-template-contract
|
||||
name: Workflow template contract
|
||||
owner: railiance-enablement
|
||||
repo: railiance-enablement
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Enablement scope
|
||||
path: /home/worsch/railiance-enablement/SCOPE.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
description: Template contract for reusable Railiance CI/CD and GitOps workflow patterns.
|
||||
interface_type: workflow-template-contract
|
||||
version: v1
|
||||
service_id: railiance-enablement.delivery-templates
|
||||
capability_ids:
|
||||
- railiance-enablement.delivery-templates.ci-cd-templates
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
@@ -23,6 +23,18 @@ spec:
|
||||
endpoint:
|
||||
url: http://127.0.0.1:8765/ui/graph-explorer
|
||||
notes: Local workstation UI when the registry service is running.
|
||||
deployment_overlay:
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
routing_authority: railiance-fabric-registry
|
||||
access_zone: private-dev
|
||||
policy_authority: local-loopback-binding
|
||||
exposure_class: local-only
|
||||
route_evidence:
|
||||
host: 127.0.0.1
|
||||
port: 8765
|
||||
protocol: tcp
|
||||
route: http://127.0.0.1:8765/ui/graph-explorer
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
|
||||
@@ -23,6 +23,18 @@ spec:
|
||||
endpoint:
|
||||
url: http://127.0.0.1:8765
|
||||
notes: Local workstation endpoint when the registry service is running.
|
||||
deployment_overlay:
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
routing_authority: local-process
|
||||
access_zone: private-dev
|
||||
policy_authority: local-loopback-binding
|
||||
exposure_class: local-only
|
||||
route_evidence:
|
||||
host: 127.0.0.1
|
||||
port: 8765
|
||||
protocol: tcp
|
||||
route: http://127.0.0.1:8765
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
|
||||
23
fabric/interfaces/railiance-forge-evidence-contract.yaml
Normal file
23
fabric/interfaces/railiance-forge-evidence-contract.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.evidence-contract
|
||||
name: Forge evidence contract
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Observability and evidence contract
|
||||
path: /home/worsch/railiance-forge/docs/observability-operating-evidence.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Release-readiness, artifact promotion, restore, storage, and operating evidence contract for forge consumers.
|
||||
interface_type: evidence-contract
|
||||
version: v1
|
||||
service_id: railiance-forge.source-forge
|
||||
capability_ids:
|
||||
- railiance-forge.source-forge.artifact-promotion-evidence
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
25
fabric/interfaces/railiance-forge-git-ssh.yaml
Normal file
25
fabric/interfaces/railiance-forge-git-ssh.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.git-ssh
|
||||
name: Git SSH endpoint
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Observability and evidence contract
|
||||
path: /home/worsch/railiance-forge/docs/observability-operating-evidence.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
description: Git-over-SSH endpoint contract for repository clone, fetch, and push operations when exposed.
|
||||
interface_type: git-ssh
|
||||
version: gitea-current
|
||||
service_id: railiance-forge.source-forge
|
||||
capability_ids:
|
||||
- railiance-forge.source-forge.source-hosting
|
||||
endpoint:
|
||||
notes: Record the published SSH host and port once the endpoint is verified.
|
||||
auth:
|
||||
method: static_secret
|
||||
data_classification: confidential
|
||||
28
fabric/interfaces/railiance-forge-oci-registry.yaml
Normal file
28
fabric/interfaces/railiance-forge-oci-registry.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.oci-registry
|
||||
name: Gitea OCI registry
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Container registry docs
|
||||
path: /home/worsch/railiance-forge/docs/gitea-container-registry.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: OCI registry endpoint served by current Gitea for Railiance container images.
|
||||
interface_type: oci-registry
|
||||
version: registry-v2
|
||||
service_id: railiance-forge.source-forge
|
||||
capability_ids:
|
||||
- railiance-forge.source-forge.container-registry
|
||||
endpoint:
|
||||
url: https://gitea.coulomb.social/v2/
|
||||
auth:
|
||||
method: api_key
|
||||
scopes:
|
||||
- package:read
|
||||
- package:write
|
||||
data_classification: confidential
|
||||
28
fabric/interfaces/railiance-forge-python-package-index.yaml
Normal file
28
fabric/interfaces/railiance-forge-python-package-index.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.python-package-index
|
||||
name: Gitea Python package index
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Package registry docs
|
||||
path: /home/worsch/railiance-forge/docs/gitea-package-registry.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Python package index endpoint served by current Gitea for internal Railiance packages.
|
||||
interface_type: python-package-index
|
||||
version: simple-api
|
||||
service_id: railiance-forge.source-forge
|
||||
capability_ids:
|
||||
- railiance-forge.source-forge.python-package-registry
|
||||
endpoint:
|
||||
url: https://gitea.coulomb.social/api/packages/coulomb/pypi/simple/
|
||||
auth:
|
||||
method: api_key
|
||||
scopes:
|
||||
- package:read
|
||||
- package:write
|
||||
data_classification: confidential
|
||||
23
fabric/interfaces/railiance-forge-runner-label-contract.yaml
Normal file
23
fabric/interfaces/railiance-forge-runner-label-contract.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.runner-label-contract
|
||||
name: Runner label contract
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Runner ownership contract
|
||||
path: /home/worsch/railiance-forge/docs/ci-runner-actions-gitops-ownership.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
description: Semantic runner labels, placement, trust levels, and credential boundaries consumed by workflow templates and release checks.
|
||||
interface_type: workflow-runner-label-contract
|
||||
version: v1
|
||||
service_id: railiance-forge.source-forge
|
||||
capability_ids:
|
||||
- railiance-forge.source-forge.workflow-runner-substrate
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
25
fabric/interfaces/railiance-forge-web-ui.yaml
Normal file
25
fabric/interfaces/railiance-forge-web-ui.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge.web-ui
|
||||
name: Source forge web UI
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Observability and evidence contract
|
||||
path: /home/worsch/railiance-forge/docs/observability-operating-evidence.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Current Gitea web UI and HTTP endpoint for source hosting and package workflows.
|
||||
interface_type: web-ui
|
||||
version: gitea-current
|
||||
service_id: railiance-forge.source-forge
|
||||
capability_ids:
|
||||
- railiance-forge.source-forge.source-hosting
|
||||
endpoint:
|
||||
url: https://gitea.coulomb.social/
|
||||
auth:
|
||||
method: unknown
|
||||
data_classification: confidential
|
||||
35
fabric/interfaces/the-custodian-state-hub-dashboard.yaml
Normal file
35
fabric/interfaces/the-custodian-state-hub-dashboard.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: the-custodian.state-hub.dashboard
|
||||
name: State Hub Dashboard
|
||||
owner: the-custodian
|
||||
repo: the-custodian
|
||||
domain: custodian
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev]
|
||||
description: Local browser dashboard for State Hub coordination views.
|
||||
interface_type: web-ui
|
||||
version: v1
|
||||
service_id: the-custodian.state-hub
|
||||
capability_ids:
|
||||
- the-custodian.state-hub.coordination
|
||||
endpoint:
|
||||
url: http://127.0.0.1:3000
|
||||
notes: Local workstation dashboard endpoint when the State Hub frontend is running.
|
||||
deployment_overlay:
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
routing_authority: vite-dev-server
|
||||
access_zone: private-dev
|
||||
policy_authority: local-loopback-binding
|
||||
exposure_class: local-only
|
||||
route_evidence:
|
||||
host: 127.0.0.1
|
||||
port: 3000
|
||||
protocol: tcp
|
||||
route: http://127.0.0.1:3000
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
@@ -15,6 +15,21 @@ spec:
|
||||
service_id: the-custodian.state-hub
|
||||
capability_ids:
|
||||
- the-custodian.state-hub.coordination
|
||||
endpoint:
|
||||
url: http://127.0.0.1:8000
|
||||
notes: Local workstation State Hub REST API.
|
||||
deployment_overlay:
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
routing_authority: state-hub-local-process
|
||||
access_zone: private-dev
|
||||
policy_authority: local-loopback-binding
|
||||
exposure_class: local-only
|
||||
route_evidence:
|
||||
host: 127.0.0.1
|
||||
port: 8000
|
||||
protocol: tcp
|
||||
route: http://127.0.0.1:8000
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
|
||||
35
fabric/interfaces/the-custodian-state-hub-mcp-api.yaml
Normal file
35
fabric/interfaces/the-custodian-state-hub-mcp-api.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: InterfaceDeclaration
|
||||
metadata:
|
||||
id: the-custodian.state-hub.mcp-api
|
||||
name: State Hub MCP API
|
||||
owner: the-custodian
|
||||
repo: the-custodian
|
||||
domain: custodian
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev]
|
||||
description: Local MCP surface for State Hub coordination tools.
|
||||
interface_type: mcp-api
|
||||
version: v1
|
||||
service_id: the-custodian.state-hub
|
||||
capability_ids:
|
||||
- the-custodian.state-hub.coordination
|
||||
endpoint:
|
||||
url: http://127.0.0.1:8001
|
||||
notes: Local workstation MCP endpoint when State Hub is running.
|
||||
deployment_overlay:
|
||||
deployment_environment: dev
|
||||
deployment_scenario: bernd-laptop
|
||||
routing_authority: state-hub-local-process
|
||||
access_zone: private-dev
|
||||
policy_authority: local-loopback-binding
|
||||
exposure_class: local-only
|
||||
route_evidence:
|
||||
host: 127.0.0.1
|
||||
port: 8001
|
||||
protocol: tcp
|
||||
route: http://127.0.0.1:8001
|
||||
auth:
|
||||
method: none
|
||||
data_classification: internal
|
||||
@@ -15,3 +15,4 @@ spec:
|
||||
- net-kingdom.iam-profile.issuer
|
||||
exposes_interfaces:
|
||||
- net-kingdom.iam-profile.oidc-discovery
|
||||
- net-kingdom.control-surface.ui
|
||||
|
||||
18
fabric/services/railiance-apps-s5-releases.yaml
Normal file
18
fabric/services/railiance-apps-s5-releases.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: ServiceDeclaration
|
||||
metadata:
|
||||
id: railiance-apps.s5-releases
|
||||
name: Railiance S5 app releases
|
||||
owner: railiance-apps
|
||||
repo: railiance-apps
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Apps scope
|
||||
path: /home/worsch/railiance-apps/SCOPE.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: S5 application release surface that consumes forge artifacts, app manifests, runbooks, dry-runs, and smoke evidence.
|
||||
service_type: app-release-surface
|
||||
provides_capabilities: []
|
||||
exposes_interfaces: []
|
||||
20
fabric/services/railiance-cluster-kubernetes.yaml
Normal file
20
fabric/services/railiance-cluster-kubernetes.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: ServiceDeclaration
|
||||
metadata:
|
||||
id: railiance-cluster.kubernetes
|
||||
name: Railiance Kubernetes runtime
|
||||
owner: railiance-cluster
|
||||
repo: railiance-cluster
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Cluster scope
|
||||
path: /home/worsch/railiance-cluster/SCOPE.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Kubernetes runtime layer that provides the API server, namespaces, workloads, Services, Ingresses, and controller substrate for Railiance services.
|
||||
service_type: cluster-runtime
|
||||
provides_capabilities:
|
||||
- railiance-cluster.kubernetes.runtime
|
||||
exposes_interfaces:
|
||||
- railiance-cluster.kubernetes.api
|
||||
22
fabric/services/railiance-enablement-delivery-templates.yaml
Normal file
22
fabric/services/railiance-enablement-delivery-templates.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: ServiceDeclaration
|
||||
metadata:
|
||||
id: railiance-enablement.delivery-templates
|
||||
name: Railiance delivery templates
|
||||
owner: railiance-enablement
|
||||
repo: railiance-enablement
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Enablement scope
|
||||
path: /home/worsch/railiance-enablement/SCOPE.md
|
||||
- label: Enablement intent
|
||||
path: /home/worsch/railiance-enablement/INTENT.md
|
||||
spec:
|
||||
lifecycle: planned
|
||||
environments: [dev, staging, prod]
|
||||
description: Reusable CI/CD and GitOps workflow template surface for Railiance workload delivery.
|
||||
service_type: enablement-template-surface
|
||||
provides_capabilities:
|
||||
- railiance-enablement.delivery-templates.ci-cd-templates
|
||||
exposes_interfaces:
|
||||
- railiance-enablement.delivery-templates.workflow-template-contract
|
||||
31
fabric/services/railiance-forge-source-forge.yaml
Normal file
31
fabric/services/railiance-forge-source-forge.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
apiVersion: railiance.fabric/v1alpha1
|
||||
kind: ServiceDeclaration
|
||||
metadata:
|
||||
id: railiance-forge.source-forge
|
||||
name: Railiance source forge
|
||||
owner: railiance-forge
|
||||
repo: railiance-forge
|
||||
domain: railiance
|
||||
source_links:
|
||||
- label: Forge scope
|
||||
path: /home/worsch/railiance-forge/SCOPE.md
|
||||
- label: Forge intent
|
||||
path: /home/worsch/railiance-forge/INTENT.md
|
||||
spec:
|
||||
lifecycle: active
|
||||
environments: [dev, staging, prod]
|
||||
description: Current Gitea source forge and future Forgejo migration surface for source hosting, registries, runner substrate, and release artifact evidence.
|
||||
service_type: forge-runtime
|
||||
provides_capabilities:
|
||||
- railiance-forge.source-forge.source-hosting
|
||||
- railiance-forge.source-forge.container-registry
|
||||
- railiance-forge.source-forge.python-package-registry
|
||||
- railiance-forge.source-forge.workflow-runner-substrate
|
||||
- railiance-forge.source-forge.artifact-promotion-evidence
|
||||
exposes_interfaces:
|
||||
- railiance-forge.source-forge.web-ui
|
||||
- railiance-forge.source-forge.git-ssh
|
||||
- railiance-forge.source-forge.oci-registry
|
||||
- railiance-forge.source-forge.python-package-index
|
||||
- railiance-forge.source-forge.runner-label-contract
|
||||
- railiance-forge.source-forge.evidence-contract
|
||||
@@ -15,3 +15,5 @@ spec:
|
||||
- the-custodian.state-hub.coordination
|
||||
exposes_interfaces:
|
||||
- the-custodian.state-hub.http-api
|
||||
- the-custodian.state-hub.mcp-api
|
||||
- the-custodian.state-hub.dashboard
|
||||
|
||||
1420
railiance_fabric/accountability_roots.py
Normal file
1420
railiance_fabric/accountability_roots.py
Normal file
File diff suppressed because it is too large
Load Diff
212
railiance_fabric/canon.py
Normal file
212
railiance_fabric/canon.py
Normal file
@@ -0,0 +1,212 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
CANONICAL_NODE_CATEGORIES = (
|
||||
"source-repository",
|
||||
"software-system",
|
||||
"service",
|
||||
"endpoint",
|
||||
"deployment",
|
||||
"runtime-resource",
|
||||
"datastore",
|
||||
"flow",
|
||||
"policy",
|
||||
"control",
|
||||
"evidence",
|
||||
"task",
|
||||
"consumer-purpose",
|
||||
"telemetry-signal",
|
||||
)
|
||||
|
||||
CANONICAL_EDGE_TYPES = (
|
||||
"built_from",
|
||||
"implements",
|
||||
"exposes",
|
||||
"depends_on",
|
||||
"deploys",
|
||||
"flows_to",
|
||||
"governed_by",
|
||||
"evidenced_by",
|
||||
"observed_by",
|
||||
"part_of",
|
||||
"reads_or_writes",
|
||||
"creates_task",
|
||||
)
|
||||
|
||||
DISPLAY_ONLY_EDGE_TYPES = (
|
||||
"collapsed_into",
|
||||
"declares",
|
||||
"grouped_with",
|
||||
"highlight_path",
|
||||
"near",
|
||||
"owns_deployment",
|
||||
"same_color_group",
|
||||
)
|
||||
|
||||
EVIDENCE_STATES = ("observed", "declared", "inferred", "proposed", "gap")
|
||||
MAPPING_FITS = ("direct", "partial", "conflict", "gap", "unknown")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanonNodeMapping:
|
||||
category: str
|
||||
canon_anchor: str
|
||||
fit: str
|
||||
notes: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanonEdgeMapping:
|
||||
canonical_type: str
|
||||
canon_anchor: str
|
||||
fit: str
|
||||
display_only: bool = False
|
||||
notes: str = ""
|
||||
|
||||
|
||||
UNKNOWN_NODE_MAPPING = CanonNodeMapping(
|
||||
category="unknown",
|
||||
canon_anchor="",
|
||||
fit="gap",
|
||||
notes="No canon mapping has been selected for this Fabric node kind yet.",
|
||||
)
|
||||
|
||||
UNKNOWN_EDGE_MAPPING = CanonEdgeMapping(
|
||||
canonical_type="",
|
||||
canon_anchor="",
|
||||
fit="gap",
|
||||
notes="No canon mapping has been selected for this Fabric edge type yet.",
|
||||
)
|
||||
|
||||
NODE_KIND_CANON_MAP: dict[str, CanonNodeMapping] = {
|
||||
"ApplicationEndpoint": CanonNodeMapping("endpoint", "model/network", "direct"),
|
||||
"BindingAssertion": CanonNodeMapping("evidence", "model/observability", "partial"),
|
||||
"CapabilityDeclaration": CanonNodeMapping("software-system", "model/landscape", "partial"),
|
||||
"ContainerBuild": CanonNodeMapping("deployment", "model/devsecops", "partial"),
|
||||
"DependencyDeclaration": CanonNodeMapping("service", "model/landscape", "gap"),
|
||||
"DeploymentService": CanonNodeMapping("deployment", "model/devsecops", "direct"),
|
||||
"DomainName": CanonNodeMapping("endpoint", "model/network", "partial"),
|
||||
"ExternalLibrary": CanonNodeMapping("software-system", "model/landscape", "partial"),
|
||||
"FabricRegistryEntry": CanonNodeMapping("source-repository", "model/devsecops", "partial"),
|
||||
"Fabric": CanonNodeMapping("control", "model/governance", "gap"),
|
||||
"FabricActor": CanonNodeMapping("control", "model/governance", "gap"),
|
||||
"InterfaceDeclaration": CanonNodeMapping("endpoint", "model/network", "partial"),
|
||||
"Library": CanonNodeMapping("software-system", "model/landscape", "partial"),
|
||||
"Lockfile": CanonNodeMapping("evidence", "model/observability", "partial"),
|
||||
"Netkingdom": CanonNodeMapping("software-system", "model/landscape", "gap"),
|
||||
"NetworkPort": CanonNodeMapping("endpoint", "model/network", "direct"),
|
||||
"ProfitCenter": CanonNodeMapping("control", "model/governance", "gap"),
|
||||
"Repository": CanonNodeMapping("source-repository", "model/devsecops", "direct"),
|
||||
"RuntimeService": CanonNodeMapping("runtime-resource", "model/landscape", "direct"),
|
||||
"ScoreWorkload": CanonNodeMapping("deployment", "model/devsecops", "direct"),
|
||||
"Server": CanonNodeMapping("runtime-resource", "model/landscape", "partial"),
|
||||
"ServiceConfig": CanonNodeMapping("evidence", "model/observability", "partial"),
|
||||
"ServiceDeclaration": CanonNodeMapping("service", "model/landscape", "direct"),
|
||||
"Subfabric": CanonNodeMapping("control", "model/governance", "gap"),
|
||||
"UtilityInterface": CanonNodeMapping("endpoint", "model/network", "partial"),
|
||||
"CostCenter": CanonNodeMapping("control", "model/governance", "gap"),
|
||||
}
|
||||
|
||||
EDGE_TYPE_CANON_MAP: dict[str, CanonEdgeMapping] = {
|
||||
"available_via": CanonEdgeMapping("exposes", "model/network", "partial"),
|
||||
"attributed_to_cost_center": CanonEdgeMapping("governed_by", "model/governance", "gap"),
|
||||
"attributed_to_profit_center": CanonEdgeMapping("governed_by", "model/governance", "gap"),
|
||||
"binds": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"builds_container": CanonEdgeMapping("built_from", "model/devsecops", "partial"),
|
||||
"cataloged_as": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
"consumes": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"contains": CanonEdgeMapping("part_of", "model/landscape", "partial"),
|
||||
"declares": CanonEdgeMapping("part_of", "model/devsecops", "partial", display_only=True),
|
||||
"declares_package": CanonEdgeMapping("built_from", "model/devsecops", "partial"),
|
||||
"defines_deployment": CanonEdgeMapping("built_from", "model/devsecops", "partial"),
|
||||
"defines_runtime_object": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"defines_workload": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"deployed_as": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"depends_on_library": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"documents_interface": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
"exposes": CanonEdgeMapping("exposes", "model/network", "direct"),
|
||||
"exposes_port": CanonEdgeMapping("exposes", "model/network", "direct"),
|
||||
"listens_on": CanonEdgeMapping("exposes", "model/network", "direct"),
|
||||
"names_endpoint": CanonEdgeMapping("exposes", "model/network", "partial"),
|
||||
"opens_port": CanonEdgeMapping("exposes", "model/network", "partial"),
|
||||
"operated_by": CanonEdgeMapping("governed_by", "model/governance", "partial"),
|
||||
"owned_by": CanonEdgeMapping("governed_by", "model/governance", "partial"),
|
||||
"owns_deployment": CanonEdgeMapping("part_of", "model/devsecops", "partial", display_only=True),
|
||||
"provides": CanonEdgeMapping("implements", "model/landscape", "partial"),
|
||||
"provides_utility_to": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"resolves_to": CanonEdgeMapping("flows_to", "model/network", "partial"),
|
||||
"routes_to_port": CanonEdgeMapping("flows_to", "model/network", "partial"),
|
||||
"routes_to_service": CanonEdgeMapping("flows_to", "model/network", "partial"),
|
||||
"runs_on": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"suggests_capability": CanonEdgeMapping("creates_task", "model/task", "partial"),
|
||||
"uses_config": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
"uses_interface": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"uses_lockfile": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
}
|
||||
|
||||
|
||||
def node_canon_mapping(kind: str) -> CanonNodeMapping:
|
||||
if kind in NODE_KIND_CANON_MAP:
|
||||
return NODE_KIND_CANON_MAP[kind]
|
||||
if kind.startswith("Kubernetes"):
|
||||
return CanonNodeMapping("runtime-resource", "model/landscape", "direct")
|
||||
return UNKNOWN_NODE_MAPPING
|
||||
|
||||
|
||||
def edge_canon_mapping(edge_type: str) -> CanonEdgeMapping:
|
||||
normalized = str(edge_type or "").strip()
|
||||
if normalized.startswith("binds:"):
|
||||
return EDGE_TYPE_CANON_MAP["binds"]
|
||||
if normalized in EDGE_TYPE_CANON_MAP:
|
||||
return EDGE_TYPE_CANON_MAP[normalized]
|
||||
if normalized in CANONICAL_EDGE_TYPES:
|
||||
return CanonEdgeMapping(normalized, _anchor_for_canonical_edge(normalized), "direct")
|
||||
if normalized in DISPLAY_ONLY_EDGE_TYPES:
|
||||
return CanonEdgeMapping("", "", "gap", display_only=True)
|
||||
return UNKNOWN_EDGE_MAPPING
|
||||
|
||||
|
||||
def evidence_state_for(
|
||||
*,
|
||||
origin: str = "",
|
||||
source_kind: str = "",
|
||||
review_state: str = "",
|
||||
confidence: float | None = None,
|
||||
) -> str:
|
||||
if review_state == "rejected":
|
||||
return "gap"
|
||||
if origin == "llm":
|
||||
return "proposed"
|
||||
if confidence is not None and confidence < 0.5:
|
||||
return "inferred"
|
||||
if source_kind in {"package_registry", "container_registry", "service_catalog", "fabric_registry"}:
|
||||
return "observed"
|
||||
if source_kind in {"llm"}:
|
||||
return "proposed"
|
||||
if not source_kind and origin == "deterministic":
|
||||
return "inferred"
|
||||
return "declared"
|
||||
|
||||
|
||||
def source_kind_from_anchor(source_anchor: dict[str, Any]) -> str:
|
||||
return str(source_anchor.get("source_kind") or "")
|
||||
|
||||
|
||||
def _anchor_for_canonical_edge(edge_type: str) -> str:
|
||||
return {
|
||||
"built_from": "model/devsecops",
|
||||
"implements": "model/security",
|
||||
"exposes": "model/network",
|
||||
"depends_on": "model/landscape",
|
||||
"deploys": "model/devsecops",
|
||||
"flows_to": "model/network",
|
||||
"governed_by": "model/governance",
|
||||
"evidenced_by": "model/observability",
|
||||
"observed_by": "model/observability",
|
||||
"part_of": "model/landscape",
|
||||
"reads_or_writes": "model/data",
|
||||
"creates_task": "model/task",
|
||||
}.get(edge_type, "")
|
||||
@@ -14,12 +14,23 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
from .accountability_roots import (
|
||||
DEFAULT_ROOT_MANIFEST_PATH,
|
||||
AccountabilityEvidenceStore,
|
||||
build_identity_projection,
|
||||
build_ownership_review,
|
||||
build_update_delta,
|
||||
collect_accountability_root_evidence,
|
||||
load_accountability_root_manifest,
|
||||
)
|
||||
from .connectors import ConnectorConfig
|
||||
from .financial_baseline import financial_export_from_legacy
|
||||
from .loader import declaration_files, load_yaml
|
||||
from .graph import FabricGraph, build_graph
|
||||
from .graph_explorer import fabric_graph_explorer_payload
|
||||
from .llm_extraction import LLMExtractionConfig
|
||||
from .reconciliation import reconcile_discovery_snapshots
|
||||
from .registry import RESET_CONFIRMATION_TOKEN
|
||||
from .scanner import EXTRACTOR_VERSION, ScanOptions, scan_repo
|
||||
from .validation import validate_roots
|
||||
|
||||
@@ -71,9 +82,9 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
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, Mermaid, or graph-explorer payload.")
|
||||
export = sub.add_parser("export", help="Export graph as JSON, Mermaid, graph-explorer, or financial payload.")
|
||||
export.add_argument("paths", nargs="*", type=Path, default=[Path(".")])
|
||||
export.add_argument("--format", choices=["json", "mermaid", "graph-explorer"], default="json")
|
||||
export.add_argument("--format", choices=["json", "mermaid", "graph-explorer", "financial"], default="json")
|
||||
|
||||
scan = sub.add_parser("scan", help="Scan a repo for deterministic discovery candidates.")
|
||||
scan.add_argument("path", nargs="?", type=Path, default=Path("."))
|
||||
@@ -106,6 +117,33 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
help="Manifest path for the local-fabric-registry connector.",
|
||||
)
|
||||
|
||||
discover_roots = sub.add_parser(
|
||||
"discover-roots",
|
||||
help="Collect raw evidence from accountability root manifest entries.",
|
||||
)
|
||||
discover_roots.add_argument("--manifest", type=Path, default=DEFAULT_ROOT_MANIFEST_PATH)
|
||||
discover_roots.add_argument("--include-remote", action="store_true", help="Allow HTTP reads from configured remote roots.")
|
||||
discover_roots.add_argument("--max-items-per-root", type=int, default=200)
|
||||
discover_roots.add_argument("--identity-projection", action="store_true", help="Print normalized identity candidates instead of raw evidence.")
|
||||
discover_roots.add_argument("--ownership-review", action="store_true", help="Print ownership resolution and review blockers.")
|
||||
discover_roots.add_argument("--delta", action="store_true", help="Print a delta against previous identity/ownership review files.")
|
||||
discover_roots.add_argument("--previous-identity-projection", type=Path, default=None)
|
||||
discover_roots.add_argument("--previous-ownership-review", type=Path, default=None)
|
||||
discover_roots.add_argument("--store-db", type=Path, default=None, help="Persist evidence and identity candidates in a SQLite store.")
|
||||
|
||||
review_identity = sub.add_parser(
|
||||
"review-identity",
|
||||
help="Persist a review decision for a stable accountability identity candidate.",
|
||||
)
|
||||
review_identity.add_argument("stable_key")
|
||||
review_identity.add_argument("--store-db", type=Path, required=True)
|
||||
review_identity.add_argument("--decision", choices=["accept", "needs_review", "reject"], required=True)
|
||||
review_identity.add_argument("--owner-actor-id", default="")
|
||||
review_identity.add_argument("--fabric-id", default="")
|
||||
review_identity.add_argument("--subfabric-id", default="")
|
||||
review_identity.add_argument("--reviewer", default="operator")
|
||||
review_identity.add_argument("--note", default="")
|
||||
|
||||
registry = sub.add_parser("registry", help="Feed a running Railiance Fabric registry service.")
|
||||
registry_sub = registry.add_subparsers(dest="registry_command", required=True)
|
||||
|
||||
@@ -240,6 +278,26 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
accept_discovery.add_argument("--accept-review-state", action="append", default=None)
|
||||
accept_discovery.add_argument("--commit", default=None)
|
||||
accept_discovery.add_argument("--json", action="store_true", help="Print the raw accept response.")
|
||||
|
||||
export_archive = registry_sub.add_parser(
|
||||
"export-reset-archive",
|
||||
help="Export registry graph/discovery data before a guarded reset.",
|
||||
)
|
||||
export_archive.add_argument("output", type=Path)
|
||||
export_archive.add_argument("--registry-url", default="http://127.0.0.1:8765")
|
||||
export_archive.add_argument("--overwrite", action="store_true", help="Overwrite an existing archive file.")
|
||||
export_archive.add_argument("--json", action="store_true", help="Print archive metadata.")
|
||||
|
||||
reset_graph = registry_sub.add_parser(
|
||||
"reset-graph-data",
|
||||
help="Export an archive, then reset registry graph/discovery data with an explicit confirmation token.",
|
||||
)
|
||||
reset_graph.add_argument("--registry-url", default="http://127.0.0.1:8765")
|
||||
reset_graph.add_argument("--archive", type=Path, required=True, help="Archive JSON file to write before reset.")
|
||||
reset_graph.add_argument("--overwrite-archive", action="store_true", help="Overwrite an existing archive file.")
|
||||
reset_graph.add_argument("--confirm", required=True, help=f"Must equal {RESET_CONFIRMATION_TOKEN}.")
|
||||
reset_graph.add_argument("--reason", required=True)
|
||||
reset_graph.add_argument("--json", action="store_true", help="Print the raw reset response.")
|
||||
return parser
|
||||
|
||||
|
||||
@@ -289,6 +347,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(graph.to_mermaid())
|
||||
elif args.format == "graph-explorer":
|
||||
print(json.dumps(fabric_graph_explorer_payload(graph.to_export()), indent=2, sort_keys=True))
|
||||
elif args.format == "financial":
|
||||
print(json.dumps(financial_export_from_legacy(graph.to_export()), indent=2, sort_keys=True))
|
||||
else:
|
||||
print(graph.to_json())
|
||||
return 0
|
||||
@@ -296,6 +356,54 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if args.command == "scan":
|
||||
return _scan_repo(args)
|
||||
|
||||
if args.command == "discover-roots":
|
||||
manifest = load_accountability_root_manifest(args.manifest)
|
||||
payload = collect_accountability_root_evidence(
|
||||
args.manifest,
|
||||
include_remote=args.include_remote,
|
||||
max_items_per_root=args.max_items_per_root,
|
||||
)
|
||||
projection = build_identity_projection(payload, manifest)
|
||||
store = AccountabilityEvidenceStore(args.store_db) if args.store_db else None
|
||||
decisions = store.latest_review_decisions() if store else {}
|
||||
ownership_review = build_ownership_review(projection, manifest, review_decisions=decisions)
|
||||
update_delta = build_update_delta(
|
||||
projection,
|
||||
ownership_review,
|
||||
previous_identity_projection=_load_json_file(args.previous_identity_projection)
|
||||
if args.previous_identity_projection
|
||||
else None,
|
||||
previous_ownership_review=_load_json_file(args.previous_ownership_review)
|
||||
if args.previous_ownership_review
|
||||
else None,
|
||||
)
|
||||
if args.store_db:
|
||||
store.add_evidence_run(payload, projection)
|
||||
output = (
|
||||
update_delta
|
||||
if args.delta
|
||||
else ownership_review
|
||||
if args.ownership_review
|
||||
else projection
|
||||
if args.identity_projection
|
||||
else payload
|
||||
)
|
||||
print(json.dumps(output, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
if args.command == "review-identity":
|
||||
decision = AccountabilityEvidenceStore(args.store_db).add_review_decision(
|
||||
stable_key=args.stable_key,
|
||||
decision=args.decision,
|
||||
reviewer=args.reviewer,
|
||||
owner_actor_id=args.owner_actor_id,
|
||||
fabric_id=args.fabric_id,
|
||||
subfabric_id=args.subfabric_id,
|
||||
note=args.note,
|
||||
)
|
||||
print(json.dumps(decision, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
if args.command == "registry":
|
||||
if args.registry_command == "sync":
|
||||
return _registry_sync(args)
|
||||
@@ -311,6 +419,10 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return _registry_ingest_discovery(args)
|
||||
if args.registry_command == "accept-discovery":
|
||||
return _registry_accept_discovery(args)
|
||||
if args.registry_command == "export-reset-archive":
|
||||
return _registry_export_reset_archive(args)
|
||||
if args.registry_command == "reset-graph-data":
|
||||
return _registry_reset_graph_data(args)
|
||||
|
||||
parser.error(f"unknown command {args.command!r}")
|
||||
return 2
|
||||
@@ -1011,7 +1123,12 @@ def _scan_manifest_exit_code(summary: dict[str, Any], args: argparse.Namespace)
|
||||
|
||||
|
||||
def _manifest_discovery_snapshot_path(base_dir: Path, slug: str, profile: str) -> Path:
|
||||
return base_dir.resolve() / f"{_slugify(slug)}-{_slugify(profile)}.discovery.json"
|
||||
slug_part = _slugify(slug)
|
||||
raw_slug_part = slug.strip().lower()
|
||||
if slug_part != raw_slug_part:
|
||||
fingerprint = hashlib.sha256(slug.encode("utf-8")).hexdigest()[:8]
|
||||
slug_part = f"{slug_part}-{fingerprint}"
|
||||
return base_dir.resolve() / f"{slug_part}-{_slugify(profile)}.discovery.json"
|
||||
|
||||
|
||||
def _candidate_counts(snapshot: dict[str, Any]) -> dict[str, int]:
|
||||
@@ -1285,6 +1402,57 @@ def _registry_accept_discovery(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _registry_export_reset_archive(args: argparse.Namespace) -> int:
|
||||
try:
|
||||
archive = _registry_get_checked(args.registry_url, "/exports/reset-archive")
|
||||
archive_sha256 = _write_json_archive(args.output, archive, overwrite=args.overwrite)
|
||||
except (RegistryRequestError, OSError) as exc:
|
||||
print(f"ERROR {exc}", file=sys.stderr)
|
||||
return 1
|
||||
metadata = {
|
||||
"archive": str(args.output),
|
||||
"archive_sha256": archive_sha256,
|
||||
"counts": archive.get("counts", {}),
|
||||
}
|
||||
if args.json:
|
||||
print(json.dumps(metadata, indent=2, sort_keys=True))
|
||||
else:
|
||||
print(f"wrote reset archive {args.output}")
|
||||
print(f"archive sha256 {archive_sha256}")
|
||||
return 0
|
||||
|
||||
|
||||
def _registry_reset_graph_data(args: argparse.Namespace) -> int:
|
||||
if args.confirm != RESET_CONFIRMATION_TOKEN:
|
||||
print(f"ERROR --confirm must equal {RESET_CONFIRMATION_TOKEN}", file=sys.stderr)
|
||||
return 1
|
||||
try:
|
||||
archive = _registry_get_checked(args.registry_url, "/exports/reset-archive")
|
||||
archive_sha256 = _write_json_archive(args.archive, archive, overwrite=args.overwrite_archive)
|
||||
result = _registry_post_checked(
|
||||
args.registry_url,
|
||||
"/admin/reset-graph-data",
|
||||
{
|
||||
"confirm": args.confirm,
|
||||
"reason": args.reason,
|
||||
"archive_path": str(args.archive),
|
||||
"archive_sha256": archive_sha256,
|
||||
},
|
||||
)
|
||||
except (RegistryRequestError, OSError) as exc:
|
||||
print(f"ERROR {exc}", file=sys.stderr)
|
||||
return 1
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
else:
|
||||
print(f"wrote reset archive {args.archive}")
|
||||
print(f"archive sha256 {archive_sha256}")
|
||||
print(f"reset event {result['id']} recorded")
|
||||
print(f"dropped {json.dumps(result.get('dropped_counts', {}), sort_keys=True)}")
|
||||
print(f"preserved {result.get('repositories_preserved', 0)} repository registration(s)")
|
||||
return 0
|
||||
|
||||
|
||||
def _scan_repo(args: argparse.Namespace) -> int:
|
||||
snapshot = scan_repo(
|
||||
ScanOptions(
|
||||
@@ -1369,6 +1537,15 @@ class RegistryRequestError(Exception):
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
def _write_json_archive(path: Path, archive: dict[str, object], *, overwrite: bool) -> str:
|
||||
if path.exists() and not overwrite:
|
||||
raise OSError(f"archive already exists: {path} (use --overwrite or --overwrite-archive)")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
data = json.dumps(archive, indent=2, sort_keys=True).encode("utf-8")
|
||||
path.write_bytes(data)
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def _registry_post(registry_url: str, path: str, payload: dict[str, object]) -> dict[str, object]:
|
||||
try:
|
||||
return _registry_post_checked(registry_url, path, payload)
|
||||
@@ -1493,6 +1670,13 @@ def _load_graph_or_exit(paths: list[Path]) -> FabricGraph:
|
||||
return graph
|
||||
|
||||
|
||||
def _load_json_file(path: Path) -> dict[str, Any]:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(f"JSON file must contain an object: {path}")
|
||||
return payload
|
||||
|
||||
|
||||
def _print_providers(graph: FabricGraph, capability: str) -> None:
|
||||
providers = graph.providers(capability)
|
||||
if not providers:
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Protocol
|
||||
|
||||
from .canon import edge_canon_mapping, evidence_state_for, node_canon_mapping
|
||||
from .discovery import (
|
||||
attribute_stable_key,
|
||||
discovery_stable_key,
|
||||
@@ -183,12 +184,22 @@ class LocalFabricRegistryConnector:
|
||||
}
|
||||
repo_key = _repository_key(context.snapshot, context.repo_slug)
|
||||
entry_key = discovery_stable_key(context.repo_slug, "FabricRegistryEntry", context.repo_slug)
|
||||
node_mapping = node_canon_mapping("FabricRegistryEntry")
|
||||
node = {
|
||||
"stable_key": entry_key,
|
||||
"kind": "FabricRegistryEntry",
|
||||
"label": str(match.get("name") or context.repo_slug),
|
||||
"repo": context.repo_slug,
|
||||
"domain": str(match.get("domain") or ""),
|
||||
"canon_category": node_mapping.category,
|
||||
"canon_anchor": node_mapping.canon_anchor,
|
||||
"mapping_fit": node_mapping.fit,
|
||||
"evidence_state": evidence_state_for(
|
||||
origin="registry",
|
||||
source_kind="fabric_registry",
|
||||
review_state="candidate",
|
||||
confidence=0.9,
|
||||
),
|
||||
"aliases": _unique_strings([context.repo_slug, match.get("name")]),
|
||||
"attributes": {
|
||||
"registry_slug": context.repo_slug,
|
||||
@@ -208,9 +219,20 @@ class LocalFabricRegistryConnector:
|
||||
"provenance": [provenance],
|
||||
"source_anchors": [anchor],
|
||||
}
|
||||
edge_mapping = edge_canon_mapping("cataloged_as")
|
||||
edge = {
|
||||
"stable_key": relationship_stable_key(repo_key, "cataloged_as", entry_key, evidence_scope=scope["id"]),
|
||||
"edge_type": "cataloged_as",
|
||||
"canonical_type": edge_mapping.canonical_type,
|
||||
"canon_anchor": edge_mapping.canon_anchor,
|
||||
"mapping_fit": edge_mapping.fit,
|
||||
"display_only": edge_mapping.display_only,
|
||||
"evidence_state": evidence_state_for(
|
||||
origin="registry",
|
||||
source_kind="fabric_registry",
|
||||
review_state="candidate",
|
||||
confidence=0.9,
|
||||
),
|
||||
"source_key": repo_key,
|
||||
"target_key": entry_key,
|
||||
"origin": "registry",
|
||||
|
||||
107
railiance_fabric/deployment_overlay.py
Normal file
107
railiance_fabric/deployment_overlay.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
OVERLAY_FIELDS = (
|
||||
"deployment_environment",
|
||||
"deployment_scenario",
|
||||
"routing_authority",
|
||||
"access_zone",
|
||||
"policy_authority",
|
||||
"exposure_class",
|
||||
)
|
||||
|
||||
ROUTE_EVIDENCE_FIELDS = (
|
||||
"host",
|
||||
"hostname",
|
||||
"port",
|
||||
"protocol",
|
||||
"route",
|
||||
"route_url",
|
||||
"path",
|
||||
"scheme",
|
||||
)
|
||||
|
||||
_ALIASES = {
|
||||
"deployment_environment": ("deployment_environment", "environment"),
|
||||
"deployment_scenario": ("deployment_scenario", "deployment_scenario_id", "scenario"),
|
||||
"routing_authority": ("routing_authority",),
|
||||
"access_zone": ("access_zone",),
|
||||
"policy_authority": ("policy_authority",),
|
||||
"exposure_class": ("exposure_class", "exposure"),
|
||||
}
|
||||
|
||||
|
||||
def normalize_deployment_overlay(*sources: object) -> dict[str, Any]:
|
||||
"""Collect deployment-zone overlay fields without changing fabric membership."""
|
||||
|
||||
overlay: dict[str, Any] = {}
|
||||
route_evidence: dict[str, Any] = {}
|
||||
|
||||
for source in sources:
|
||||
if not isinstance(source, dict):
|
||||
continue
|
||||
nested = source.get("deployment_overlay")
|
||||
if isinstance(nested, dict):
|
||||
_merge_overlay_source(overlay, route_evidence, nested)
|
||||
_merge_overlay_source(overlay, route_evidence, source)
|
||||
|
||||
if route_evidence:
|
||||
overlay["route_evidence"] = route_evidence
|
||||
return {key: value for key, value in overlay.items() if _has_value(value)}
|
||||
|
||||
|
||||
def graph_explorer_overlay_fields(overlay: dict[str, Any]) -> dict[str, Any]:
|
||||
route = overlay.get("route_evidence") if isinstance(overlay.get("route_evidence"), dict) else {}
|
||||
return {
|
||||
"deploymentOverlay": overlay,
|
||||
"deploymentEnvironment": str(overlay.get("deployment_environment") or ""),
|
||||
"deploymentScenario": str(overlay.get("deployment_scenario") or ""),
|
||||
"routingAuthority": str(overlay.get("routing_authority") or ""),
|
||||
"accessZone": str(overlay.get("access_zone") or ""),
|
||||
"policyAuthority": str(overlay.get("policy_authority") or ""),
|
||||
"exposureClass": str(overlay.get("exposure_class") or ""),
|
||||
"routeHost": str(route.get("host") or ""),
|
||||
"routeHostname": str(route.get("hostname") or ""),
|
||||
"routePort": route.get("port") if route.get("port") not in (None, "") else "",
|
||||
"routeProtocol": str(route.get("protocol") or ""),
|
||||
"route": str(route.get("route") or route.get("route_url") or ""),
|
||||
}
|
||||
|
||||
|
||||
def _merge_overlay_source(overlay: dict[str, Any], route_evidence: dict[str, Any], source: dict[str, Any]) -> None:
|
||||
for field in OVERLAY_FIELDS:
|
||||
if field in overlay:
|
||||
continue
|
||||
value = _first_value(source, _ALIASES[field])
|
||||
if _has_value(value):
|
||||
overlay[field] = value
|
||||
|
||||
nested_route = source.get("route_evidence")
|
||||
if isinstance(nested_route, dict):
|
||||
for field in ROUTE_EVIDENCE_FIELDS:
|
||||
value = nested_route.get(field)
|
||||
if _has_value(value) and field not in route_evidence:
|
||||
route_evidence[field] = value
|
||||
|
||||
for field in ROUTE_EVIDENCE_FIELDS:
|
||||
value = source.get(field)
|
||||
if _has_value(value) and field not in route_evidence:
|
||||
route_evidence[field] = value
|
||||
|
||||
|
||||
def _first_value(source: dict[str, Any], keys: tuple[str, ...]) -> Any:
|
||||
for key in keys:
|
||||
value = source.get(key)
|
||||
if _has_value(value):
|
||||
return value
|
||||
return ""
|
||||
|
||||
|
||||
def _has_value(value: Any) -> bool:
|
||||
if isinstance(value, dict):
|
||||
return any(_has_value(item) for item in value.values())
|
||||
if isinstance(value, list):
|
||||
return any(_has_value(item) for item in value)
|
||||
return value not in (None, "")
|
||||
429
railiance_fabric/financial.py
Normal file
429
railiance_fabric/financial.py
Normal file
@@ -0,0 +1,429 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from .deployment_overlay import normalize_deployment_overlay
|
||||
|
||||
FINANCIAL_API_VERSION = "railiance.fabric/v1alpha2"
|
||||
FINANCIAL_SCHEMA_VERSION = "financial-fabric-v1"
|
||||
|
||||
ACTOR_ROLES = {"king", "lord", "tenant", "operator", "steward"}
|
||||
OWNERSHIP_RESOLUTIONS = {"explicit", "inherited", "unresolved", "ambiguous"}
|
||||
EVIDENCE_STATES = {"observed", "declared", "inferred", "proposed", "gap"}
|
||||
REVIEW_STATES = {"accepted", "candidate", "needs_review", "rejected"}
|
||||
RELATIONSHIP_CATEGORIES = {
|
||||
"containment",
|
||||
"ownership",
|
||||
"technical",
|
||||
"utility",
|
||||
"accounting",
|
||||
"evidence",
|
||||
}
|
||||
FABRIC_KINDS = {"Fabric", "Subfabric"}
|
||||
ENVIRONMENT_LABELS = {"local", "dev", "test", "staging", "prod", "lab", "all"}
|
||||
|
||||
|
||||
def is_financial_graph_export(graph: dict[str, Any]) -> bool:
|
||||
return (
|
||||
graph.get("apiVersion") == FINANCIAL_API_VERSION
|
||||
or graph.get("schema_version") == FINANCIAL_SCHEMA_VERSION
|
||||
)
|
||||
|
||||
|
||||
def materialize_financial_graph_export(graph: dict[str, Any]) -> dict[str, Any]:
|
||||
result = json.loads(json.dumps(graph))
|
||||
if not is_financial_graph_export(result):
|
||||
return result
|
||||
|
||||
result["apiVersion"] = FINANCIAL_API_VERSION
|
||||
result.setdefault("kind", "FabricGraphExport")
|
||||
result.setdefault("schema_version", FINANCIAL_SCHEMA_VERSION)
|
||||
result.setdefault("compatibility", {})
|
||||
result.setdefault("actors", [])
|
||||
result.setdefault("fabrics", [])
|
||||
result.setdefault("nodes", [])
|
||||
result.setdefault("edges", [])
|
||||
result.setdefault("unresolved", [])
|
||||
|
||||
for node in _items(result, "nodes"):
|
||||
_materialize_node(node)
|
||||
for edge in _items(result, "edges"):
|
||||
_materialize_edge(edge)
|
||||
return result
|
||||
|
||||
|
||||
def financial_graph_errors(graph: dict[str, Any]) -> list[str]:
|
||||
if not is_financial_graph_export(graph):
|
||||
return []
|
||||
|
||||
errors: list[str] = []
|
||||
if graph.get("apiVersion") != FINANCIAL_API_VERSION:
|
||||
errors.append(f"apiVersion must be {FINANCIAL_API_VERSION!r}")
|
||||
if graph.get("kind") != "FabricGraphExport":
|
||||
errors.append("kind must be 'FabricGraphExport'")
|
||||
if graph.get("schema_version") != FINANCIAL_SCHEMA_VERSION:
|
||||
errors.append(f"schema_version must be {FINANCIAL_SCHEMA_VERSION!r}")
|
||||
|
||||
netkingdom = graph.get("netkingdom")
|
||||
if not isinstance(netkingdom, dict):
|
||||
errors.append("netkingdom must be an object")
|
||||
netkingdom = {}
|
||||
netkingdom_id = _required_text(errors, "netkingdom.id", netkingdom, "id")
|
||||
king_actor_id = _required_text(errors, "netkingdom.king_actor_id", netkingdom, "king_actor_id")
|
||||
|
||||
actors = _indexed_items(errors, graph, "actors")
|
||||
actor_roles: dict[str, str] = {}
|
||||
for index, actor in actors:
|
||||
actor_id = _required_text(errors, f"actors[{index}].id", actor, "id")
|
||||
role = _required_text(errors, f"actors[{index}].role", actor, "role")
|
||||
if role and role not in ACTOR_ROLES:
|
||||
errors.append(f"actors[{index}].role {role!r} is not valid")
|
||||
if actor_id:
|
||||
if actor_id in actor_roles:
|
||||
errors.append(f"actors[{index}].id {actor_id!r} is duplicated")
|
||||
actor_roles[actor_id] = role
|
||||
if king_actor_id and actor_roles.get(king_actor_id) != "king":
|
||||
errors.append("netkingdom.king_actor_id must reference an actor with role 'king'")
|
||||
|
||||
fabrics = _indexed_items(errors, graph, "fabrics")
|
||||
fabric_kinds: dict[str, str] = {}
|
||||
for index, fabric in fabrics:
|
||||
fabric_id = _required_text(errors, f"fabrics[{index}].id", fabric, "id")
|
||||
kind = _required_text(errors, f"fabrics[{index}].kind", fabric, "kind")
|
||||
if kind and kind not in FABRIC_KINDS:
|
||||
errors.append(f"fabrics[{index}].kind {kind!r} is not valid")
|
||||
if fabric_id:
|
||||
if fabric_id in ENVIRONMENT_LABELS:
|
||||
errors.append(f"fabrics[{index}].id must not be an environment label")
|
||||
if fabric_id in fabric_kinds:
|
||||
errors.append(f"fabrics[{index}].id {fabric_id!r} is duplicated")
|
||||
fabric_kinds[fabric_id] = kind
|
||||
if _text(fabric.get("netkingdom_id")) != netkingdom_id:
|
||||
errors.append(f"fabrics[{index}].netkingdom_id must match netkingdom.id")
|
||||
if kind == "Fabric":
|
||||
lord_actor_id = _required_text(errors, f"fabrics[{index}].lord_actor_id", fabric, "lord_actor_id")
|
||||
if lord_actor_id and actor_roles.get(lord_actor_id) not in {"lord", "king"}:
|
||||
errors.append(f"fabrics[{index}].lord_actor_id must reference a lord or king actor")
|
||||
if kind == "Subfabric":
|
||||
parent_id = _required_text(errors, f"fabrics[{index}].parent_fabric_id", fabric, "parent_fabric_id")
|
||||
tenant_actor_id = _required_text(errors, f"fabrics[{index}].tenant_actor_id", fabric, "tenant_actor_id")
|
||||
if parent_id and parent_id not in fabric_kinds:
|
||||
errors.append(f"fabrics[{index}].parent_fabric_id references unknown fabric {parent_id!r}")
|
||||
if tenant_actor_id and actor_roles.get(tenant_actor_id) != "tenant":
|
||||
errors.append(f"fabrics[{index}].tenant_actor_id must reference a tenant actor")
|
||||
|
||||
node_ids = set()
|
||||
nodes = _indexed_items(errors, graph, "nodes")
|
||||
for index, node in nodes:
|
||||
node_id = _required_text(errors, f"nodes[{index}].id", node, "id")
|
||||
_required_text(errors, f"nodes[{index}].kind", node, "kind")
|
||||
_required_text(errors, f"nodes[{index}].name", node, "name")
|
||||
if node_id:
|
||||
if node_id in node_ids:
|
||||
errors.append(f"nodes[{index}].id {node_id!r} is duplicated")
|
||||
node_ids.add(node_id)
|
||||
review_state = _validate_evidence(errors, f"nodes[{index}]", node)
|
||||
_validate_containment(errors, f"nodes[{index}]", node, netkingdom_id, fabric_kinds, accepted=review_state == "accepted")
|
||||
_validate_ownership(errors, f"nodes[{index}]", node, actor_roles, accepted=review_state == "accepted")
|
||||
_validate_optional_object(errors, f"nodes[{index}].accounting", node, "accounting")
|
||||
_validate_overlay(errors, f"nodes[{index}].deployment_overlay", node)
|
||||
|
||||
edges = _indexed_items(errors, graph, "edges")
|
||||
for index, edge in edges:
|
||||
path = f"edges[{index}]"
|
||||
source = _required_text(errors, f"{path}.from", edge, "from")
|
||||
target = _required_text(errors, f"{path}.to", edge, "to")
|
||||
edge_type = _required_text(errors, f"{path}.type", edge, "type")
|
||||
if source and source not in node_ids:
|
||||
errors.append(f"{path}.from references unknown node {source!r}")
|
||||
if target and target not in node_ids:
|
||||
errors.append(f"{path}.to references unknown node {target!r}")
|
||||
category = _required_text(errors, f"{path}.relationship_category", edge, "relationship_category")
|
||||
if category and category not in RELATIONSHIP_CATEGORIES:
|
||||
errors.append(f"{path}.relationship_category {category!r} is not valid")
|
||||
_validate_evidence(errors, path, edge)
|
||||
_validate_optional_object(errors, f"{path}.accounting", edge, "accounting")
|
||||
_validate_overlay(errors, f"{path}.deployment_overlay", edge)
|
||||
if edge_type == "provides_utility_to" and category != "utility":
|
||||
errors.append(f"{path}.relationship_category must be 'utility' for provides_utility_to edges")
|
||||
if category == "utility":
|
||||
_validate_utility_edge(errors, path, edge, actor_roles, fabric_kinds)
|
||||
|
||||
for index, item in _indexed_items(errors, graph, "unresolved"):
|
||||
_required_text(errors, f"unresolved[{index}].target_id", item, "target_id")
|
||||
_required_text(errors, f"unresolved[{index}].kind", item, "kind")
|
||||
_required_text(errors, f"unresolved[{index}].message", item, "message")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def merge_financial_graph_exports(graphs: list[dict[str, Any]], *, generated_at: str) -> dict[str, Any]:
|
||||
merged: dict[str, Any] = {
|
||||
"apiVersion": FINANCIAL_API_VERSION,
|
||||
"kind": "FabricGraphExport",
|
||||
"schema_version": FINANCIAL_SCHEMA_VERSION,
|
||||
"generated_at": generated_at,
|
||||
"source": {"producer": "railiance-fabric", "registry": "registry", "commit": ""},
|
||||
"compatibility": {"legacy_v1alpha1_supported": True, "breaking_reset": False},
|
||||
"actors": [],
|
||||
"fabrics": [],
|
||||
"nodes": [],
|
||||
"edges": [],
|
||||
"unresolved": [],
|
||||
}
|
||||
actors: dict[str, dict[str, Any]] = {}
|
||||
fabrics: dict[str, dict[str, Any]] = {}
|
||||
nodes: dict[str, dict[str, Any]] = {}
|
||||
edges: dict[str, dict[str, Any]] = {}
|
||||
unresolved: dict[str, dict[str, Any]] = {}
|
||||
|
||||
for graph in graphs:
|
||||
graph = materialize_financial_graph_export(graph)
|
||||
if "netkingdom" in graph and "netkingdom" not in merged:
|
||||
merged["netkingdom"] = graph["netkingdom"]
|
||||
for actor in _items(graph, "actors"):
|
||||
actor_id = str(actor.get("id") or "")
|
||||
if actor_id:
|
||||
actors[actor_id] = actor
|
||||
for fabric in _items(graph, "fabrics"):
|
||||
fabric_id = str(fabric.get("id") or "")
|
||||
if fabric_id:
|
||||
fabrics[fabric_id] = fabric
|
||||
for node in _items(graph, "nodes"):
|
||||
node_id = str(node.get("id") or "")
|
||||
if node_id:
|
||||
nodes[node_id] = node
|
||||
for edge in _items(graph, "edges"):
|
||||
edges[_edge_identity(edge)] = edge
|
||||
for item in _items(graph, "unresolved"):
|
||||
target = str(item.get("target_id") or "")
|
||||
kind = str(item.get("kind") or "")
|
||||
if target or kind:
|
||||
unresolved[f"{target}:{kind}"] = item
|
||||
|
||||
merged["actors"] = [actors[key] for key in sorted(actors)]
|
||||
merged["fabrics"] = [fabrics[key] for key in sorted(fabrics)]
|
||||
merged["nodes"] = [nodes[key] for key in sorted(nodes)]
|
||||
merged["edges"] = [edges[key] for key in sorted(edges)]
|
||||
merged["unresolved"] = [unresolved[key] for key in sorted(unresolved)]
|
||||
return materialize_financial_graph_export(merged)
|
||||
|
||||
|
||||
def _materialize_node(node: dict[str, Any]) -> None:
|
||||
attrs = node.get("attributes") if isinstance(node.get("attributes"), dict) else {}
|
||||
for key in ("containment", "ownership", "accounting"):
|
||||
if key not in node and isinstance(attrs.get(key), dict):
|
||||
node[key] = attrs[key]
|
||||
overlay = normalize_deployment_overlay(
|
||||
node.get("deployment_overlay") if isinstance(node.get("deployment_overlay"), dict) else {},
|
||||
attrs,
|
||||
node.get("containment") if isinstance(node.get("containment"), dict) else {},
|
||||
)
|
||||
if overlay:
|
||||
node["deployment_overlay"] = overlay
|
||||
node.setdefault("evidence", _legacy_evidence(node, attrs))
|
||||
|
||||
|
||||
def _materialize_edge(edge: dict[str, Any]) -> None:
|
||||
attrs = edge.get("attributes") if isinstance(edge.get("attributes"), dict) else {}
|
||||
if "accounting" not in edge and isinstance(attrs.get("accounting"), dict):
|
||||
edge["accounting"] = attrs["accounting"]
|
||||
overlay = normalize_deployment_overlay(
|
||||
edge.get("deployment_overlay") if isinstance(edge.get("deployment_overlay"), dict) else {},
|
||||
attrs,
|
||||
)
|
||||
if overlay:
|
||||
edge["deployment_overlay"] = overlay
|
||||
edge.setdefault("relationship_category", _relationship_category(edge))
|
||||
edge.setdefault("evidence", _legacy_evidence(edge, attrs))
|
||||
if edge.get("relationship_category") == "utility":
|
||||
provider = edge.get("provider") if isinstance(edge.get("provider"), dict) else {}
|
||||
consumer = edge.get("consumer") if isinstance(edge.get("consumer"), dict) else {}
|
||||
boundary = edge.setdefault("boundary", {})
|
||||
if isinstance(boundary, dict) and provider and consumer:
|
||||
boundary.setdefault("crosses_fabric_boundary", provider.get("fabric_id") != consumer.get("fabric_id"))
|
||||
boundary.setdefault("crosses_subfabric_boundary", provider.get("subfabric_id") != consumer.get("subfabric_id"))
|
||||
|
||||
|
||||
def _legacy_evidence(item: dict[str, Any], attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
confidence = attrs.get("discovery_confidence")
|
||||
evidence: dict[str, Any] = {
|
||||
"state": item.get("evidence_state") or "declared",
|
||||
"review_state": attrs.get("discovery_review_state") or "accepted",
|
||||
"refs": attrs.get("discovery_source_anchors") if isinstance(attrs.get("discovery_source_anchors"), list) else [],
|
||||
}
|
||||
if isinstance(confidence, (int, float)):
|
||||
evidence["confidence"] = float(confidence)
|
||||
return evidence
|
||||
|
||||
|
||||
def _relationship_category(edge: dict[str, Any]) -> str:
|
||||
edge_type = str(edge.get("type") or "")
|
||||
if edge_type in {"contains", "part_of"}:
|
||||
return "containment"
|
||||
if edge_type in {"owned_by", "operated_by"}:
|
||||
return "ownership"
|
||||
if edge_type == "provides_utility_to":
|
||||
return "utility"
|
||||
if edge_type in {"attributed_to_cost_center", "attributed_to_profit_center"}:
|
||||
return "accounting"
|
||||
if edge_type == "evidenced_by":
|
||||
return "evidence"
|
||||
return "technical"
|
||||
|
||||
|
||||
def _validate_evidence(errors: list[str], path: str, item: dict[str, Any]) -> str:
|
||||
evidence = item.get("evidence")
|
||||
if not isinstance(evidence, dict):
|
||||
errors.append(f"{path}.evidence must be an object")
|
||||
return ""
|
||||
state = _required_text(errors, f"{path}.evidence.state", evidence, "state")
|
||||
review_state = _required_text(errors, f"{path}.evidence.review_state", evidence, "review_state")
|
||||
if state and state not in EVIDENCE_STATES:
|
||||
errors.append(f"{path}.evidence.state {state!r} is not valid")
|
||||
if review_state and review_state not in REVIEW_STATES:
|
||||
errors.append(f"{path}.evidence.review_state {review_state!r} is not valid")
|
||||
return review_state
|
||||
|
||||
|
||||
def _validate_containment(
|
||||
errors: list[str],
|
||||
path: str,
|
||||
node: dict[str, Any],
|
||||
netkingdom_id: str,
|
||||
fabric_kinds: dict[str, str],
|
||||
*,
|
||||
accepted: bool,
|
||||
) -> None:
|
||||
containment = node.get("containment")
|
||||
if not isinstance(containment, dict):
|
||||
if accepted:
|
||||
errors.append(f"{path}.containment must be an object for accepted nodes")
|
||||
return
|
||||
if accepted:
|
||||
if _text(containment.get("netkingdom_id")) != netkingdom_id:
|
||||
errors.append(f"{path}.containment.netkingdom_id must match netkingdom.id")
|
||||
fabric_id = _required_text(errors, f"{path}.containment.fabric_id", containment, "fabric_id")
|
||||
if fabric_id:
|
||||
if fabric_id in ENVIRONMENT_LABELS:
|
||||
errors.append(f"{path}.containment.fabric_id must not be an environment label")
|
||||
if fabric_id not in fabric_kinds:
|
||||
errors.append(f"{path}.containment.fabric_id references unknown fabric {fabric_id!r}")
|
||||
subfabric_id = _text(containment.get("subfabric_id"))
|
||||
if subfabric_id:
|
||||
if fabric_kinds.get(subfabric_id) != "Subfabric":
|
||||
errors.append(f"{path}.containment.subfabric_id references unknown subfabric {subfabric_id!r}")
|
||||
environment = _text(containment.get("environment"))
|
||||
if environment and environment in fabric_kinds:
|
||||
errors.append(f"{path}.containment.environment must not reference a fabric id")
|
||||
|
||||
|
||||
def _validate_ownership(
|
||||
errors: list[str],
|
||||
path: str,
|
||||
node: dict[str, Any],
|
||||
actor_roles: dict[str, str],
|
||||
*,
|
||||
accepted: bool,
|
||||
) -> None:
|
||||
ownership = node.get("ownership")
|
||||
if not isinstance(ownership, dict):
|
||||
if accepted:
|
||||
errors.append(f"{path}.ownership must be an object for accepted nodes")
|
||||
return
|
||||
owner_actor_id = _required_text(errors, f"{path}.ownership.owner_actor_id", ownership, "owner_actor_id")
|
||||
owner_role = _required_text(errors, f"{path}.ownership.owner_role", ownership, "owner_role")
|
||||
resolution = _required_text(errors, f"{path}.ownership.resolution", ownership, "resolution")
|
||||
if owner_role and owner_role not in ACTOR_ROLES:
|
||||
errors.append(f"{path}.ownership.owner_role {owner_role!r} is not valid")
|
||||
if resolution and resolution not in OWNERSHIP_RESOLUTIONS:
|
||||
errors.append(f"{path}.ownership.resolution {resolution!r} is not valid")
|
||||
if owner_actor_id and owner_actor_id not in actor_roles:
|
||||
errors.append(f"{path}.ownership.owner_actor_id references unknown actor {owner_actor_id!r}")
|
||||
if owner_actor_id and owner_role and actor_roles.get(owner_actor_id) not in {owner_role, ""}:
|
||||
errors.append(f"{path}.ownership.owner_role does not match referenced actor role")
|
||||
if accepted and resolution in {"unresolved", "ambiguous"}:
|
||||
errors.append(f"{path}.ownership.resolution must be explicit or inherited for accepted nodes")
|
||||
|
||||
|
||||
def _validate_utility_edge(
|
||||
errors: list[str],
|
||||
path: str,
|
||||
edge: dict[str, Any],
|
||||
actor_roles: dict[str, str],
|
||||
fabric_kinds: dict[str, str],
|
||||
) -> None:
|
||||
provider = _required_object(errors, f"{path}.provider", edge, "provider")
|
||||
consumer = _required_object(errors, f"{path}.consumer", edge, "consumer")
|
||||
boundary = _required_object(errors, f"{path}.boundary", edge, "boundary")
|
||||
utility = _required_object(errors, f"{path}.utility", edge, "utility")
|
||||
for side_name, side in (("provider", provider), ("consumer", consumer)):
|
||||
actor_id = _required_text(errors, f"{path}.{side_name}.owner_actor_id", side, "owner_actor_id")
|
||||
fabric_id = _required_text(errors, f"{path}.{side_name}.fabric_id", side, "fabric_id")
|
||||
if actor_id and actor_id not in actor_roles:
|
||||
errors.append(f"{path}.{side_name}.owner_actor_id references unknown actor {actor_id!r}")
|
||||
if fabric_id and fabric_id not in fabric_kinds:
|
||||
errors.append(f"{path}.{side_name}.fabric_id references unknown fabric {fabric_id!r}")
|
||||
subfabric_id = _text(side.get("subfabric_id"))
|
||||
if subfabric_id and fabric_kinds.get(subfabric_id) != "Subfabric":
|
||||
errors.append(f"{path}.{side_name}.subfabric_id references unknown subfabric {subfabric_id!r}")
|
||||
_required_text(errors, f"{path}.utility.utility_type", utility, "utility_type")
|
||||
for key in ("crosses_fabric_boundary", "crosses_subfabric_boundary"):
|
||||
if not isinstance(boundary.get(key), bool):
|
||||
errors.append(f"{path}.boundary.{key} must be a boolean")
|
||||
|
||||
|
||||
def _validate_optional_object(errors: list[str], path: str, item: dict[str, Any], key: str) -> None:
|
||||
if key in item and not isinstance(item[key], dict):
|
||||
errors.append(f"{path} must be an object")
|
||||
|
||||
|
||||
def _validate_overlay(errors: list[str], path: str, item: dict[str, Any]) -> None:
|
||||
overlay = item.get("deployment_overlay")
|
||||
if overlay is None:
|
||||
return
|
||||
if not isinstance(overlay, dict):
|
||||
errors.append(f"{path} must be an object")
|
||||
return
|
||||
route = overlay.get("route_evidence")
|
||||
if route is not None and not isinstance(route, dict):
|
||||
errors.append(f"{path}.route_evidence must be an object")
|
||||
|
||||
|
||||
def _required_object(errors: list[str], path: str, item: dict[str, Any], key: str) -> dict[str, Any]:
|
||||
value = item.get(key)
|
||||
if not isinstance(value, dict):
|
||||
errors.append(f"{path} must be an object")
|
||||
return {}
|
||||
return value
|
||||
|
||||
|
||||
def _required_text(errors: list[str], path: str, item: dict[str, Any], key: str) -> str:
|
||||
value = _text(item.get(key))
|
||||
if not value:
|
||||
errors.append(f"{path} is required")
|
||||
return value
|
||||
|
||||
|
||||
def _text(value: Any) -> str:
|
||||
return value.strip() if isinstance(value, str) else ""
|
||||
|
||||
|
||||
def _indexed_items(errors: list[str], graph: dict[str, Any], key: str) -> list[tuple[int, dict[str, Any]]]:
|
||||
value = graph.get(key, [])
|
||||
if not isinstance(value, list):
|
||||
errors.append(f"{key} must be an array")
|
||||
return []
|
||||
return [(index, item) for index, item in enumerate(value) if isinstance(item, dict)]
|
||||
|
||||
|
||||
def _items(graph: dict[str, Any], key: str) -> list[dict[str, Any]]:
|
||||
value = graph.get(key, [])
|
||||
return [item for item in value if isinstance(item, dict)] if isinstance(value, list) else []
|
||||
|
||||
|
||||
def _edge_identity(edge: dict[str, Any]) -> str:
|
||||
if edge.get("id"):
|
||||
return str(edge["id"])
|
||||
return f"{edge.get('from', '')}:{edge.get('type', '')}:{edge.get('to', '')}"
|
||||
155
railiance_fabric/financial_baseline.py
Normal file
155
railiance_fabric/financial_baseline.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .deployment_overlay import normalize_deployment_overlay
|
||||
from .financial import (
|
||||
FINANCIAL_API_VERSION,
|
||||
FINANCIAL_SCHEMA_VERSION,
|
||||
financial_graph_errors,
|
||||
materialize_financial_graph_export,
|
||||
)
|
||||
from .loader import load_yaml, repo_root
|
||||
|
||||
DEFAULT_BASELINE_PATH = repo_root() / "fabric" / "financial" / "railiance-netkingdom.yaml"
|
||||
|
||||
|
||||
def load_financial_baseline(path: Path | None = None) -> dict[str, Any]:
|
||||
baseline_path = path or DEFAULT_BASELINE_PATH
|
||||
data = load_yaml(baseline_path)
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"financial baseline must be a mapping: {baseline_path}")
|
||||
if data.get("kind") != "FinancialFabricBaseline":
|
||||
raise ValueError(f"financial baseline kind must be FinancialFabricBaseline: {baseline_path}")
|
||||
spec = data.get("spec")
|
||||
if not isinstance(spec, dict):
|
||||
raise ValueError(f"financial baseline spec must be a mapping: {baseline_path}")
|
||||
return spec
|
||||
|
||||
|
||||
def financial_export_from_legacy(
|
||||
legacy_graph: dict[str, Any],
|
||||
baseline: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
baseline = json.loads(json.dumps(baseline or load_financial_baseline()))
|
||||
defaults = baseline.get("defaults") if isinstance(baseline.get("defaults"), dict) else {}
|
||||
containment_default = defaults.get("containment") if isinstance(defaults.get("containment"), dict) else {}
|
||||
ownership_default = defaults.get("ownership") if isinstance(defaults.get("ownership"), dict) else {}
|
||||
accounting_default = defaults.get("accounting") if isinstance(defaults.get("accounting"), dict) else {}
|
||||
|
||||
graph = {
|
||||
"apiVersion": FINANCIAL_API_VERSION,
|
||||
"kind": "FabricGraphExport",
|
||||
"schema_version": FINANCIAL_SCHEMA_VERSION,
|
||||
"source": {
|
||||
**_object(legacy_graph.get("source")),
|
||||
"producer": "railiance-fabric",
|
||||
"generation_reason": "baseline_projection",
|
||||
},
|
||||
"compatibility": {
|
||||
"legacy_v1alpha1_supported": True,
|
||||
"breaking_reset": False,
|
||||
"projected_from_apiVersion": legacy_graph.get("apiVersion"),
|
||||
},
|
||||
"netkingdom": baseline.get("netkingdom", {}),
|
||||
"actors": baseline.get("actors", []),
|
||||
"fabrics": baseline.get("fabrics", []),
|
||||
"nodes": [
|
||||
_financial_node_from_legacy(node, containment_default, ownership_default, accounting_default)
|
||||
for node in legacy_graph.get("nodes", [])
|
||||
if isinstance(node, dict)
|
||||
],
|
||||
"edges": [
|
||||
_financial_edge_from_legacy(edge)
|
||||
for edge in legacy_graph.get("edges", [])
|
||||
if isinstance(edge, dict)
|
||||
],
|
||||
"unresolved": [],
|
||||
}
|
||||
graph["generated_at"] = legacy_graph.get("generated_at") or _utc_now()
|
||||
materialized = materialize_financial_graph_export(graph)
|
||||
errors = financial_graph_errors(materialized)
|
||||
if errors:
|
||||
raise ValueError(f"invalid financial baseline projection: {errors[0]}")
|
||||
return materialized
|
||||
|
||||
|
||||
def _financial_node_from_legacy(
|
||||
node: dict[str, Any],
|
||||
containment_default: dict[str, Any],
|
||||
ownership_default: dict[str, Any],
|
||||
accounting_default: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {
|
||||
"id": node.get("id", ""),
|
||||
"kind": _node_kind(node),
|
||||
"name": node.get("name") or node.get("id", ""),
|
||||
"repo": node.get("repo", ""),
|
||||
"domain": node.get("domain", ""),
|
||||
"lifecycle": node.get("lifecycle", ""),
|
||||
"containment": json.loads(json.dumps(containment_default)),
|
||||
"ownership": json.loads(json.dumps(ownership_default)),
|
||||
"evidence_state": node.get("evidence_state", "declared"),
|
||||
"attributes": _object(node.get("attributes")),
|
||||
}
|
||||
accounting = _object(result["attributes"].get("accounting")) or accounting_default
|
||||
if _has_value(accounting):
|
||||
result["accounting"] = json.loads(json.dumps(accounting))
|
||||
overlay = normalize_deployment_overlay(
|
||||
node.get("deployment_overlay") if isinstance(node.get("deployment_overlay"), dict) else {},
|
||||
result["attributes"],
|
||||
result["containment"],
|
||||
)
|
||||
if _has_value(overlay):
|
||||
result["deployment_overlay"] = overlay
|
||||
for key in ("canon_category", "canon_anchor", "mapping_fit"):
|
||||
if node.get(key):
|
||||
result[key] = node[key]
|
||||
return result
|
||||
|
||||
|
||||
def _financial_edge_from_legacy(edge: dict[str, Any]) -> dict[str, Any]:
|
||||
result = {
|
||||
"from": edge.get("from", ""),
|
||||
"to": edge.get("to", ""),
|
||||
"type": edge.get("type", ""),
|
||||
"attributes": _object(edge.get("attributes")),
|
||||
}
|
||||
for key in ("canonical_type", "canon_anchor", "mapping_fit", "display_only", "evidence_state"):
|
||||
if key in edge:
|
||||
result[key] = edge[key]
|
||||
overlay = normalize_deployment_overlay(
|
||||
edge.get("deployment_overlay") if isinstance(edge.get("deployment_overlay"), dict) else {},
|
||||
result["attributes"],
|
||||
)
|
||||
if _has_value(overlay):
|
||||
result["deployment_overlay"] = overlay
|
||||
return result
|
||||
|
||||
|
||||
def _node_kind(node: dict[str, Any]) -> str:
|
||||
kind = str(node.get("kind") or "")
|
||||
if kind == "ServiceDeclaration":
|
||||
return "Service"
|
||||
if kind == "InterfaceDeclaration":
|
||||
return "UtilityInterface"
|
||||
return kind or "DiscoveredEntity"
|
||||
|
||||
|
||||
def _object(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
def _has_value(value: Any) -> bool:
|
||||
if isinstance(value, dict):
|
||||
return any(_has_value(item) for item in value.values())
|
||||
if isinstance(value, list):
|
||||
return any(_has_value(item) for item in value)
|
||||
return value not in (None, "")
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .loader import load_declarations
|
||||
from .canon import edge_canon_mapping, node_canon_mapping
|
||||
from .model import Declaration
|
||||
|
||||
|
||||
@@ -186,6 +187,7 @@ class FabricGraph:
|
||||
edges: list[dict[str, str]] = []
|
||||
|
||||
for declaration in sorted(self.declarations, key=lambda item: (item.kind, item.id)):
|
||||
canon_mapping = node_canon_mapping(declaration.kind)
|
||||
nodes.append(
|
||||
{
|
||||
"id": declaration.id,
|
||||
@@ -194,35 +196,39 @@ class FabricGraph:
|
||||
"repo": declaration.metadata.get("repo", ""),
|
||||
"domain": declaration.metadata.get("domain", ""),
|
||||
"lifecycle": declaration.spec.get("lifecycle", ""),
|
||||
"canon_category": canon_mapping.category,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"evidence_state": "declared",
|
||||
"attributes": _export_attributes(declaration),
|
||||
}
|
||||
)
|
||||
|
||||
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"})
|
||||
edges.append(_export_edge(service.id, capability_id, "provides"))
|
||||
for interface_id in service.spec.get("exposes_interfaces", []):
|
||||
edges.append({"from": service.id, "to": interface_id, "type": "exposes"})
|
||||
edges.append(_export_edge(service.id, interface_id, "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"})
|
||||
edges.append(_export_edge(capability.id, interface_id, "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"})
|
||||
edges.append(_export_edge(consumer, dependency.id, "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', '')}",
|
||||
}
|
||||
_export_edge(
|
||||
dependency.id,
|
||||
str(binding.spec.get("provider_capability_id", "")),
|
||||
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"})
|
||||
edges.append(_export_edge(dependency.id, interface_id, "uses_interface"))
|
||||
|
||||
return {
|
||||
"apiVersion": "railiance.fabric/v1alpha1",
|
||||
@@ -265,6 +271,20 @@ def _escape_mermaid(value: str) -> str:
|
||||
return value.replace('"', '\\"')
|
||||
|
||||
|
||||
def _export_edge(source: str, target: str, edge_type: str) -> dict[str, Any]:
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
return {
|
||||
"from": source,
|
||||
"to": target,
|
||||
"type": edge_type,
|
||||
"canonical_type": canon_mapping.canonical_type,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"display_only": canon_mapping.display_only,
|
||||
"evidence_state": "declared",
|
||||
}
|
||||
|
||||
|
||||
def _export_attributes(declaration: Declaration) -> dict[str, Any]:
|
||||
spec = declaration.spec
|
||||
base = _base_export_attributes(declaration)
|
||||
@@ -320,9 +340,12 @@ def _export_attributes(declaration: Declaration) -> dict[str, Any]:
|
||||
|
||||
def _base_export_attributes(declaration: Declaration) -> dict[str, Any]:
|
||||
source_links = declaration.metadata.get("source_links", [])
|
||||
return {
|
||||
attributes = {
|
||||
"owner": declaration.metadata.get("owner", ""),
|
||||
"description": declaration.spec.get("description", ""),
|
||||
"source_path": str(declaration.path),
|
||||
"source_links": source_links if isinstance(source_links, list) else [],
|
||||
}
|
||||
if isinstance(declaration.spec.get("deployment_overlay"), dict):
|
||||
attributes["deployment_overlay"] = declaration.spec["deployment_overlay"]
|
||||
return attributes
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
from datetime import datetime, timezone
|
||||
from re import sub
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from .canon import edge_canon_mapping
|
||||
from .deployment_overlay import graph_explorer_overlay_fields, normalize_deployment_overlay
|
||||
|
||||
|
||||
DISPLAY_STATES = ("show", "blur", "hide", "highlight", "remove")
|
||||
LAYER_ORDER = (
|
||||
"repository",
|
||||
"server",
|
||||
"runtime_service",
|
||||
"application",
|
||||
"network",
|
||||
"domain",
|
||||
"deployment",
|
||||
"service",
|
||||
"capability",
|
||||
@@ -22,6 +30,10 @@ LAYER_ORDER = (
|
||||
_KIND_LAYER = {
|
||||
"Repository": "repository",
|
||||
"Server": "server",
|
||||
"RuntimeService": "runtime_service",
|
||||
"ApplicationEndpoint": "application",
|
||||
"NetworkPort": "network",
|
||||
"DomainName": "domain",
|
||||
"Deployment": "deployment",
|
||||
"ServiceDeclaration": "service",
|
||||
"CapabilityDeclaration": "capability",
|
||||
@@ -34,6 +46,10 @@ _KIND_LAYER = {
|
||||
_LAYER_COLORS = {
|
||||
"repository": "#475569",
|
||||
"server": "#334155",
|
||||
"runtime_service": "#0369a1",
|
||||
"application": "#0f766e",
|
||||
"network": "#64748b",
|
||||
"domain": "#9333ea",
|
||||
"deployment": "#16a34a",
|
||||
"service": "#0f766e",
|
||||
"capability": "#2563eb",
|
||||
@@ -44,13 +60,30 @@ _LAYER_COLORS = {
|
||||
}
|
||||
|
||||
_EDGE_STRENGTH = {
|
||||
"provides": "strong",
|
||||
"built_from": "medium",
|
||||
"depends_on": "medium",
|
||||
"deploys": "strong",
|
||||
"evidenced_by": "medium",
|
||||
"exposes": "strong",
|
||||
"flows_to": "medium",
|
||||
"governed_by": "medium",
|
||||
"implements": "medium",
|
||||
"observed_by": "medium",
|
||||
"part_of": "weak",
|
||||
"reads_or_writes": "medium",
|
||||
"provides": "strong",
|
||||
"available_via": "medium",
|
||||
"consumes": "medium",
|
||||
"uses_interface": "medium",
|
||||
"declares": "weak",
|
||||
"deployed_as": "medium",
|
||||
"exposes_port": "strong",
|
||||
"listens_on": "strong",
|
||||
"names_endpoint": "medium",
|
||||
"opens_port": "strong",
|
||||
"routes_to_port": "medium",
|
||||
"routes_to_service": "strong",
|
||||
"resolves_to": "medium",
|
||||
"runs_on": "strong",
|
||||
"owns_deployment": "weak",
|
||||
}
|
||||
@@ -102,6 +135,12 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
"layer",
|
||||
"kind",
|
||||
"environment",
|
||||
"deploymentEnvironment",
|
||||
"deploymentScenario",
|
||||
"routingAuthority",
|
||||
"accessZone",
|
||||
"policyAuthority",
|
||||
"exposureClass",
|
||||
"serverHost",
|
||||
"lifecycle",
|
||||
"unresolved",
|
||||
@@ -115,10 +154,23 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
"repo",
|
||||
"domain",
|
||||
"environment",
|
||||
"deploymentEnvironment",
|
||||
"deploymentScenario",
|
||||
"routingAuthority",
|
||||
"accessZone",
|
||||
"policyAuthority",
|
||||
"exposureClass",
|
||||
"serverHost",
|
||||
"routeHost",
|
||||
"routeHostname",
|
||||
"routePort",
|
||||
"routeProtocol",
|
||||
"kind",
|
||||
"layer",
|
||||
"edgeType",
|
||||
"canonicalType",
|
||||
"canonCategory",
|
||||
"evidenceState",
|
||||
],
|
||||
"filter": {
|
||||
"actions": list(DISPLAY_STATES),
|
||||
@@ -128,11 +180,23 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
{"id": "repo", "label": "Repo", "type": "string"},
|
||||
{"id": "domain", "label": "Domain", "type": "string"},
|
||||
{"id": "environment", "label": "Environment", "type": "string"},
|
||||
{"id": "deploymentEnvironment", "label": "Deployment Environment", "type": "string"},
|
||||
{"id": "deploymentScenario", "label": "Deployment Scenario", "type": "string"},
|
||||
{"id": "routingAuthority", "label": "Routing Authority", "type": "string"},
|
||||
{"id": "accessZone", "label": "Access Zone", "type": "string"},
|
||||
{"id": "policyAuthority", "label": "Policy Authority", "type": "string"},
|
||||
{"id": "exposureClass", "label": "Exposure Class", "type": "string"},
|
||||
{"id": "serverHost", "label": "Server Host", "type": "string"},
|
||||
{"id": "routeHost", "label": "Route Host", "type": "string"},
|
||||
{"id": "routePort", "label": "Route Port", "type": "number"},
|
||||
{"id": "lifecycle", "label": "Lifecycle", "type": "string"},
|
||||
{"id": "reviewState", "label": "Review State", "type": "string"},
|
||||
{"id": "unresolved", "label": "Unresolved", "type": "boolean"},
|
||||
{"id": "edgeType", "label": "Edge Type", "type": "string"},
|
||||
{"id": "canonicalType", "label": "Canonical Edge", "type": "string"},
|
||||
{"id": "canonCategory", "label": "Canon Category", "type": "string"},
|
||||
{"id": "evidenceState", "label": "Evidence State", "type": "string"},
|
||||
{"id": "displayOnly", "label": "Display Only", "type": "boolean"},
|
||||
{"id": "strength", "label": "Strength", "type": "string"},
|
||||
{"id": "sameLayer", "label": "Same Layer", "type": "boolean"},
|
||||
{"id": "text", "label": "Text", "type": "string"},
|
||||
@@ -154,6 +218,16 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
"repo",
|
||||
"domain",
|
||||
"lifecycle",
|
||||
"deploymentEnvironment",
|
||||
"deploymentScenario",
|
||||
"routingAuthority",
|
||||
"accessZone",
|
||||
"policyAuthority",
|
||||
"exposureClass",
|
||||
"routeHost",
|
||||
"routePort",
|
||||
"canonCategory",
|
||||
"evidenceState",
|
||||
"unresolved",
|
||||
"description",
|
||||
"sourceReferences",
|
||||
@@ -161,6 +235,9 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
],
|
||||
"edge_fields": [
|
||||
"edgeType",
|
||||
"canonicalType",
|
||||
"displayOnly",
|
||||
"evidenceState",
|
||||
"strength",
|
||||
"source",
|
||||
"target",
|
||||
@@ -171,6 +248,31 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
],
|
||||
},
|
||||
"modes": [
|
||||
{
|
||||
"id": "by-fabric",
|
||||
"label": "Fabric",
|
||||
"description": "Group and filter by fabric/accountability fields.",
|
||||
},
|
||||
{
|
||||
"id": "by-deployment-environment",
|
||||
"label": "Environment",
|
||||
"description": "Group and filter by deployment environment.",
|
||||
},
|
||||
{
|
||||
"id": "by-deployment-scenario",
|
||||
"label": "Scenario",
|
||||
"description": "Group and filter by deployment scenario.",
|
||||
},
|
||||
{
|
||||
"id": "by-routing-authority",
|
||||
"label": "Routing",
|
||||
"description": "Group and filter by routing authority.",
|
||||
},
|
||||
{
|
||||
"id": "by-access-zone",
|
||||
"label": "Access Zone",
|
||||
"description": "Group and filter by intended access zone.",
|
||||
},
|
||||
{
|
||||
"id": "full",
|
||||
"label": "Full",
|
||||
@@ -238,6 +340,13 @@ def fabric_graph_explorer_payload(
|
||||
|
||||
elements: list[dict[str, Any]] = []
|
||||
repository_index = {str(repo.get("slug", "")): repo for repo in repositories}
|
||||
source_repository_node_ids = {
|
||||
str(node.get("id", "")): f"repo:{str(node.get('repo', '')).strip()}"
|
||||
for node in source_nodes
|
||||
if str(node.get("kind", "")) == "Repository"
|
||||
and str(node.get("id", ""))
|
||||
and str(node.get("repo", "")).strip()
|
||||
}
|
||||
for slug in sorted(repo_slugs):
|
||||
repo = repository_index.get(slug, {})
|
||||
has_snapshot = slug in snapshot_repo_slugs or slug in source_repo_slugs
|
||||
@@ -280,10 +389,17 @@ def fabric_graph_explorer_payload(
|
||||
node_id = str(node.get("id", ""))
|
||||
if not node_id:
|
||||
continue
|
||||
kind = str(node.get("kind", ""))
|
||||
source_kind = str(node.get("kind", ""))
|
||||
if source_kind == "Repository":
|
||||
continue
|
||||
attributes = node.get("attributes") if isinstance(node.get("attributes"), dict) else {}
|
||||
overlay_data = _overlay_data(
|
||||
node.get("deployment_overlay") if isinstance(node.get("deployment_overlay"), dict) else {},
|
||||
attributes,
|
||||
)
|
||||
kind = _presentation_kind(source_kind, attributes)
|
||||
layer = _layer_for_kind(kind)
|
||||
is_unresolved = node_id in unresolved
|
||||
attributes = node.get("attributes") if isinstance(node.get("attributes"), dict) else {}
|
||||
review_state = str(attributes.get("discovery_review_state") or "accepted")
|
||||
confidence = attributes.get("discovery_confidence")
|
||||
elements.append(
|
||||
@@ -299,6 +415,16 @@ def fabric_graph_explorer_payload(
|
||||
"repo": str(node.get("repo", "")),
|
||||
"domain": str(node.get("domain", "")),
|
||||
"lifecycle": str(node.get("lifecycle", "")),
|
||||
"environment": overlay_data["deploymentEnvironment"],
|
||||
**overlay_data,
|
||||
"canonCategory": str(
|
||||
node.get("canon_category") or attributes.get("canon_category") or ""
|
||||
),
|
||||
"canonAnchor": str(node.get("canon_anchor") or attributes.get("canon_anchor") or ""),
|
||||
"mappingFit": str(node.get("mapping_fit") or attributes.get("mapping_fit") or ""),
|
||||
"evidenceState": str(
|
||||
node.get("evidence_state") or attributes.get("evidence_state") or ""
|
||||
),
|
||||
"reviewState": review_state,
|
||||
"freshnessState": "current",
|
||||
"unresolved": is_unresolved,
|
||||
@@ -309,7 +435,7 @@ def fabric_graph_explorer_payload(
|
||||
),
|
||||
"visualSize": 34 if layer == "binding" else 46 if is_unresolved else 50,
|
||||
"ownership": str(attributes.get("owner") or attributes.get("discovery_origin") or "repo"),
|
||||
"attributes": attributes,
|
||||
"attributes": {**attributes, "source_kind": source_kind} if source_kind != kind else attributes,
|
||||
"discovery": {
|
||||
"stableKey": attributes.get("discovery_stable_key", ""),
|
||||
"origin": attributes.get("discovery_origin", ""),
|
||||
@@ -333,6 +459,7 @@ def fabric_graph_explorer_payload(
|
||||
|
||||
node_layers = _node_data_index(elements, "layer")
|
||||
node_repos = _node_data_index(elements, "repo")
|
||||
node_kinds = _node_data_index(elements, "kind")
|
||||
edge_index = _append_infrastructure_elements(
|
||||
source_nodes,
|
||||
elements,
|
||||
@@ -342,21 +469,43 @@ def fabric_graph_explorer_payload(
|
||||
)
|
||||
|
||||
for edge in source_edges:
|
||||
source = str(edge.get("from", ""))
|
||||
target = str(edge.get("to", ""))
|
||||
edge_type = str(edge.get("type", ""))
|
||||
source = source_repository_node_ids.get(str(edge.get("from", "")), str(edge.get("from", "")))
|
||||
target = source_repository_node_ids.get(str(edge.get("to", "")), str(edge.get("to", "")))
|
||||
edge_type = _presentation_edge_type(str(edge.get("type", "")), source, target, node_kinds)
|
||||
if not source or not target:
|
||||
continue
|
||||
elements.append(_edge_element(edge_index, source, target, edge_type, node_layers, node_repos))
|
||||
elements.append(
|
||||
_edge_element(
|
||||
edge_index,
|
||||
source,
|
||||
target,
|
||||
edge_type,
|
||||
node_layers,
|
||||
node_repos,
|
||||
**_edge_metadata(edge, edge_type),
|
||||
)
|
||||
)
|
||||
edge_index += 1
|
||||
|
||||
for slug in sorted(repo_slugs):
|
||||
repo_id = f"repo:{slug}"
|
||||
for node in source_nodes:
|
||||
node_id = str(node.get("id", ""))
|
||||
if str(node.get("kind", "")) == "Repository":
|
||||
continue
|
||||
if str(node.get("repo", "")) != slug or not node_id:
|
||||
continue
|
||||
elements.append(_edge_element(edge_index, repo_id, node_id, "declares", node_layers, node_repos))
|
||||
elements.append(
|
||||
_edge_element(
|
||||
edge_index,
|
||||
repo_id,
|
||||
node_id,
|
||||
"declares",
|
||||
node_layers,
|
||||
node_repos,
|
||||
display_only=True,
|
||||
)
|
||||
)
|
||||
edge_index += 1
|
||||
|
||||
visible_nodes = [element for element in elements if "source" not in element["data"]]
|
||||
@@ -411,6 +560,46 @@ def _layer_for_kind(kind: str) -> str:
|
||||
return _KIND_LAYER.get(kind, kind.lower() or "unknown")
|
||||
|
||||
|
||||
def _presentation_kind(kind: str, attributes: dict[str, Any]) -> str:
|
||||
if kind != "Server":
|
||||
return kind
|
||||
host = str(attributes.get("host") or "").strip().lower()
|
||||
runtime_type = str(attributes.get("runtime_target_type") or attributes.get("server_type") or "")
|
||||
if runtime_type == "kubernetes-service-dns" or host.endswith(".svc.cluster.local"):
|
||||
return "RuntimeService"
|
||||
if runtime_type in {"declared-endpoint", "ingress-host"} and _looks_like_domain(host):
|
||||
return "ApplicationEndpoint"
|
||||
return kind
|
||||
|
||||
|
||||
def _presentation_edge_type(edge_type: str, source: str, target: str, node_kinds: dict[str, str]) -> str:
|
||||
if edge_type == "resolves_to":
|
||||
target_kind = node_kinds.get(target, "")
|
||||
if target_kind == "ApplicationEndpoint":
|
||||
return "names_endpoint"
|
||||
if target_kind == "RuntimeService":
|
||||
return "routes_to_service"
|
||||
if edge_type == "opens_port" and node_kinds.get(source, "") in {"ApplicationEndpoint", "RuntimeService"}:
|
||||
return "listens_on"
|
||||
return edge_type
|
||||
|
||||
|
||||
def _edge_metadata(edge: dict[str, Any], edge_type: str) -> dict[str, Any]:
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
attributes = edge.get("attributes") if isinstance(edge.get("attributes"), dict) else {}
|
||||
return {
|
||||
"canonical_type": str(edge.get("canonical_type") or canon_mapping.canonical_type),
|
||||
"canon_anchor": str(edge.get("canon_anchor") or canon_mapping.canon_anchor),
|
||||
"mapping_fit": str(edge.get("mapping_fit") or canon_mapping.fit),
|
||||
"display_only": bool(edge.get("display_only", canon_mapping.display_only)),
|
||||
"evidence_state": str(edge.get("evidence_state") or "declared"),
|
||||
"deployment_overlay": normalize_deployment_overlay(
|
||||
edge.get("deployment_overlay") if isinstance(edge.get("deployment_overlay"), dict) else {},
|
||||
attributes,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _edge_strength(edge_type: str) -> str:
|
||||
if edge_type.startswith("binds:"):
|
||||
status = edge_type.split(":", 1)[1]
|
||||
@@ -437,6 +626,26 @@ def _node_data_index(elements: list[dict[str, Any]], field: str) -> dict[str, st
|
||||
return index
|
||||
|
||||
|
||||
def _runtime_node_indexes(source_nodes: list[dict[str, Any]]) -> tuple[dict[str, str], dict[str, str]]:
|
||||
servers_by_host: dict[str, str] = {}
|
||||
ports_by_endpoint: dict[str, str] = {}
|
||||
for node in source_nodes:
|
||||
node_id = str(node.get("id") or "")
|
||||
if not node_id:
|
||||
continue
|
||||
attributes = node.get("attributes") if isinstance(node.get("attributes"), dict) else {}
|
||||
kind = _presentation_kind(str(node.get("kind") or ""), attributes)
|
||||
host = _normalize_endpoint_host(str(attributes.get("host") or ""))
|
||||
if kind == "Server" and host:
|
||||
servers_by_host.setdefault(host, node_id)
|
||||
if kind == "NetworkPort" and host:
|
||||
port = _int_value(attributes.get("port"))
|
||||
protocol = _normalize_protocol(str(attributes.get("protocol") or "tcp"))
|
||||
if port is not None:
|
||||
ports_by_endpoint.setdefault(_endpoint_key(host, port, protocol), node_id)
|
||||
return servers_by_host, ports_by_endpoint
|
||||
|
||||
|
||||
def _append_infrastructure_elements(
|
||||
source_nodes: list[dict[str, Any]],
|
||||
elements: list[dict[str, Any]],
|
||||
@@ -446,7 +655,29 @@ def _append_infrastructure_elements(
|
||||
) -> int:
|
||||
edge_index = 0
|
||||
endpoints_by_service = _endpoints_by_service(source_nodes)
|
||||
server_ids_by_host: dict[str, str] = {}
|
||||
server_ids_by_host, port_ids_by_endpoint = _runtime_node_indexes(source_nodes)
|
||||
generated_edge_keys: set[tuple[str, str, str]] = set()
|
||||
|
||||
def append_edge(source: str, target: str, edge_type: str, *, display_only: bool = True) -> None:
|
||||
nonlocal edge_index
|
||||
if not source or not target:
|
||||
return
|
||||
key = (source, edge_type, target)
|
||||
if key in generated_edge_keys:
|
||||
return
|
||||
generated_edge_keys.add(key)
|
||||
elements.append(
|
||||
_edge_element(
|
||||
edge_index,
|
||||
source,
|
||||
target,
|
||||
edge_type,
|
||||
node_layers,
|
||||
node_repos,
|
||||
display_only=display_only,
|
||||
)
|
||||
)
|
||||
edge_index += 1
|
||||
|
||||
service_nodes = sorted(
|
||||
(node for node in source_nodes if node.get("kind") == "ServiceDeclaration"),
|
||||
@@ -468,6 +699,12 @@ def _append_infrastructure_elements(
|
||||
for endpoint in service_endpoints
|
||||
if _environment_matches(environment, endpoint["environments"])
|
||||
]
|
||||
deployment_overlay = normalize_deployment_overlay(
|
||||
attributes,
|
||||
{"deployment_environment": environment},
|
||||
*[endpoint["deployment_overlay"] for endpoint in matching_endpoints],
|
||||
)
|
||||
overlay_data = _overlay_data(deployment_overlay)
|
||||
server_hosts = sorted({endpoint["host"] for endpoint in matching_endpoints})
|
||||
deployment_data = {
|
||||
"id": deployment_id,
|
||||
@@ -481,6 +718,7 @@ def _append_infrastructure_elements(
|
||||
"domain": str(service.get("domain") or ""),
|
||||
"lifecycle": str(service.get("lifecycle") or ""),
|
||||
"environment": environment,
|
||||
**overlay_data,
|
||||
"serviceId": service_id,
|
||||
"serverHosts": server_hosts,
|
||||
"reviewState": "accepted",
|
||||
@@ -505,31 +743,40 @@ def _append_infrastructure_elements(
|
||||
node_layers[deployment_id] = "deployment"
|
||||
node_repos[deployment_id] = repo
|
||||
|
||||
elements.append(_edge_element(edge_index, service_id, deployment_id, "deployed_as", node_layers, node_repos))
|
||||
edge_index += 1
|
||||
append_edge(service_id, deployment_id, "deployed_as")
|
||||
if repo and repo in repo_slugs:
|
||||
repo_id = f"repo:{repo}"
|
||||
elements.append(_edge_element(edge_index, repo_id, deployment_id, "owns_deployment", node_layers, node_repos))
|
||||
edge_index += 1
|
||||
append_edge(repo_id, deployment_id, "owns_deployment")
|
||||
|
||||
for endpoint in matching_endpoints:
|
||||
server_id = server_ids_by_host.get(endpoint["host"])
|
||||
if server_id is None:
|
||||
server_id = _server_id(endpoint["host"])
|
||||
server_ids_by_host[endpoint["host"]] = server_id
|
||||
host = endpoint["host"]
|
||||
port = endpoint["port"]
|
||||
protocol = endpoint["protocol"]
|
||||
endpoint_overlay_data = _overlay_data(
|
||||
endpoint["deployment_overlay"],
|
||||
{"deployment_environment": environment},
|
||||
)
|
||||
server_id = server_ids_by_host.get(host)
|
||||
endpoint_key = _endpoint_key(host, port, protocol)
|
||||
port_id = port_ids_by_endpoint.get(endpoint_key)
|
||||
port_was_generated = port_id is None
|
||||
if server_id is None and _looks_like_machine_address(host):
|
||||
server_id = _server_id(host)
|
||||
server_ids_by_host[host] = server_id
|
||||
server_data = {
|
||||
"id": server_id,
|
||||
"stableKey": server_id,
|
||||
"kind": "Server",
|
||||
"layer": "server",
|
||||
"label": endpoint["host"],
|
||||
"name": endpoint["host"],
|
||||
"label": host,
|
||||
"name": host,
|
||||
"description": f"Server inferred from endpoint {endpoint['url']}.",
|
||||
"repo": "",
|
||||
"domain": str(service.get("domain") or ""),
|
||||
"lifecycle": "active",
|
||||
"environment": environment,
|
||||
"serverHost": endpoint["host"],
|
||||
**endpoint_overlay_data,
|
||||
"serverHost": host,
|
||||
"reviewState": "accepted",
|
||||
"freshnessState": "current",
|
||||
"unresolved": False,
|
||||
@@ -537,9 +784,10 @@ def _append_infrastructure_elements(
|
||||
"visualSize": 48,
|
||||
"ownership": "inferred",
|
||||
"attributes": {
|
||||
"host": endpoint["host"],
|
||||
"host": host,
|
||||
"source_interface_id": endpoint["interface_id"],
|
||||
"endpoint_url": endpoint["url"],
|
||||
"deployment_overlay": endpoint_overlay_data["deploymentOverlay"],
|
||||
},
|
||||
"displayState": "show",
|
||||
"visibilitySource": "default",
|
||||
@@ -550,8 +798,51 @@ def _append_infrastructure_elements(
|
||||
elements.append({"data": server_data, "classes": "server accepted inferred"})
|
||||
node_layers[server_id] = "server"
|
||||
node_repos[server_id] = ""
|
||||
elements.append(_edge_element(edge_index, deployment_id, server_id, "runs_on", node_layers, node_repos))
|
||||
edge_index += 1
|
||||
if port_id is None:
|
||||
port_id = _port_id(host, port, protocol)
|
||||
port_ids_by_endpoint[endpoint_key] = port_id
|
||||
port_data = {
|
||||
"id": port_id,
|
||||
"stableKey": port_id,
|
||||
"kind": "NetworkPort",
|
||||
"layer": "network",
|
||||
"label": f"{host}:{port}/{protocol}",
|
||||
"name": f"{host}:{port}/{protocol}",
|
||||
"description": f"Port inferred from endpoint {endpoint['url']}.",
|
||||
"repo": "",
|
||||
"domain": str(service.get("domain") or ""),
|
||||
"lifecycle": "active",
|
||||
"environment": environment,
|
||||
**endpoint_overlay_data,
|
||||
"serverHost": host,
|
||||
"reviewState": "accepted",
|
||||
"freshnessState": "current",
|
||||
"unresolved": False,
|
||||
"confidence": 0.7,
|
||||
"visualSize": 42,
|
||||
"ownership": "inferred",
|
||||
"attributes": {
|
||||
"host": host,
|
||||
"port": port,
|
||||
"protocol": protocol,
|
||||
"source_interface_id": endpoint["interface_id"],
|
||||
"endpoint_url": endpoint["url"],
|
||||
"deployment_overlay": endpoint_overlay_data["deploymentOverlay"],
|
||||
},
|
||||
"displayState": "show",
|
||||
"visibilitySource": "default",
|
||||
"visibilityReason": "default",
|
||||
"sourceReferences": [{"label": "Endpoint interface", "ref": endpoint["interface_id"]}],
|
||||
"deepLinks": {"interface": f"/graph/nodes/{endpoint['interface_id']}"},
|
||||
}
|
||||
elements.append({"data": port_data, "classes": "network accepted inferred"})
|
||||
node_layers[port_id] = "network"
|
||||
node_repos[port_id] = ""
|
||||
if server_id:
|
||||
append_edge(deployment_id, server_id, "runs_on")
|
||||
if port_was_generated:
|
||||
append_edge(server_id, port_id, "opens_port")
|
||||
append_edge(deployment_id, port_id, "exposes_port")
|
||||
return edge_index
|
||||
|
||||
|
||||
@@ -563,16 +854,29 @@ def _endpoints_by_service(source_nodes: list[dict[str, Any]]) -> dict[str, list[
|
||||
attributes = node.get("attributes") if isinstance(node.get("attributes"), dict) else {}
|
||||
endpoint = attributes.get("endpoint") if isinstance(attributes.get("endpoint"), dict) else {}
|
||||
url = str(endpoint.get("url") or "").strip()
|
||||
host = _endpoint_host(url)
|
||||
parsed = _parse_endpoint_url(url)
|
||||
service_id = str(attributes.get("service_id") or "")
|
||||
if not service_id or not host:
|
||||
if not service_id or not parsed:
|
||||
continue
|
||||
host, port, protocol = parsed
|
||||
endpoints.setdefault(service_id, []).append(
|
||||
{
|
||||
"host": host,
|
||||
"port": port,
|
||||
"protocol": protocol,
|
||||
"url": url,
|
||||
"interface_id": str(node.get("id") or ""),
|
||||
"environments": _environments(attributes),
|
||||
"deployment_overlay": normalize_deployment_overlay(
|
||||
attributes,
|
||||
endpoint,
|
||||
{
|
||||
"host": host,
|
||||
"port": port,
|
||||
"protocol": protocol,
|
||||
"route": url,
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
return endpoints
|
||||
@@ -596,11 +900,70 @@ def _environment_matches(deployment_environment: str, endpoint_environments: lis
|
||||
|
||||
|
||||
def _endpoint_host(url: str) -> str:
|
||||
parsed = _parse_endpoint_url(url)
|
||||
return parsed[0] if parsed else ""
|
||||
|
||||
|
||||
def _parse_endpoint_url(url: str) -> tuple[str, int, str] | None:
|
||||
if not url:
|
||||
return ""
|
||||
return None
|
||||
parsed = urlparse(url)
|
||||
host = parsed.netloc or parsed.path.split("/", 1)[0]
|
||||
return host.strip().lower()
|
||||
host = _normalize_endpoint_host(parsed.hostname or parsed.netloc or parsed.path.split("/", 1)[0])
|
||||
try:
|
||||
port = parsed.port
|
||||
except ValueError:
|
||||
port = None
|
||||
scheme = str(parsed.scheme or "").lower()
|
||||
port = port or {"http": 80, "https": 443, "postgres": 5432}.get(scheme)
|
||||
if not host or port is None:
|
||||
return None
|
||||
return host, port, "tcp"
|
||||
|
||||
|
||||
def _normalize_endpoint_host(host: str) -> str:
|
||||
value = str(host or "").strip().lower().strip("[]")
|
||||
if value in {"0.0.0.0", "::"}:
|
||||
return "localhost"
|
||||
return value
|
||||
|
||||
|
||||
def _endpoint_key(host: str, port: int, protocol: str) -> str:
|
||||
return f"{_normalize_endpoint_host(host)}:{port}/{_normalize_protocol(protocol)}"
|
||||
|
||||
|
||||
def _normalize_protocol(protocol: str) -> str:
|
||||
return str(protocol or "tcp").strip().lower() or "tcp"
|
||||
|
||||
|
||||
def _int_value(value: object) -> int | None:
|
||||
if isinstance(value, bool):
|
||||
return None
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
text = str(value or "").strip()
|
||||
if text.isdecimal():
|
||||
return int(text)
|
||||
return None
|
||||
|
||||
|
||||
def _looks_like_domain(host: str) -> bool:
|
||||
value = _normalize_endpoint_host(host)
|
||||
if not value or value == "localhost":
|
||||
return False
|
||||
if all(part.isdecimal() for part in value.split(".") if part):
|
||||
return False
|
||||
return "." in value
|
||||
|
||||
|
||||
def _looks_like_machine_address(host: str) -> bool:
|
||||
value = _normalize_endpoint_host(host)
|
||||
if value in {"localhost", "127.0.0.1"}:
|
||||
return True
|
||||
try:
|
||||
ipaddress.ip_address(value)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _server_id(host: str) -> str:
|
||||
@@ -608,6 +971,11 @@ def _server_id(host: str) -> str:
|
||||
return f"server:{key or 'unknown'}"
|
||||
|
||||
|
||||
def _port_id(host: str, port: int, protocol: str) -> str:
|
||||
key = sub(r"[^A-Za-z0-9._:+-]+", "-", _endpoint_key(host, port, protocol)).strip("-")
|
||||
return f"port:{key or 'unknown'}"
|
||||
|
||||
|
||||
def _edge_element(
|
||||
edge_index: int,
|
||||
source: str,
|
||||
@@ -615,15 +983,29 @@ def _edge_element(
|
||||
edge_type: str,
|
||||
node_layers: dict[str, str],
|
||||
node_repos: dict[str, str],
|
||||
*,
|
||||
canonical_type: str = "",
|
||||
canon_anchor: str = "",
|
||||
mapping_fit: str = "",
|
||||
display_only: bool = False,
|
||||
evidence_state: str = "",
|
||||
deployment_overlay: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
source_layer = node_layers.get(source, "unknown")
|
||||
target_layer = node_layers.get(target, "unknown")
|
||||
source_repo = node_repos.get(source, "")
|
||||
target_repo = node_repos.get(target, "")
|
||||
same_repo = bool(source_repo and source_repo == target_repo)
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
canonical_type = canonical_type or canon_mapping.canonical_type
|
||||
canon_anchor = canon_anchor or canon_mapping.canon_anchor
|
||||
mapping_fit = mapping_fit or canon_mapping.fit
|
||||
display_only = display_only or canon_mapping.display_only
|
||||
evidence_state = evidence_state or "declared"
|
||||
strength = _edge_strength(edge_type)
|
||||
layout = _layout_hints(edge_type, source_layer, target_layer, same_repo)
|
||||
edge_id = f"edge:{edge_index}:{source}:{edge_type}:{target}"
|
||||
overlay_data = _overlay_data(deployment_overlay or {})
|
||||
return {
|
||||
"data": {
|
||||
"id": edge_id,
|
||||
@@ -639,6 +1021,12 @@ def _edge_element(
|
||||
"targetRepo": target_repo,
|
||||
"edgeType": edge_type,
|
||||
"dependencyType": edge_type,
|
||||
"canonicalType": canonical_type,
|
||||
"canonAnchor": canon_anchor,
|
||||
"mappingFit": mapping_fit,
|
||||
"displayOnly": display_only,
|
||||
"evidenceState": evidence_state,
|
||||
**overlay_data,
|
||||
"strength": strength,
|
||||
"edgeWidth": _edge_width(strength),
|
||||
"sameLayer": source_layer == target_layer,
|
||||
@@ -659,6 +1047,7 @@ def _edge_element(
|
||||
edge_type.replace(":", "-"),
|
||||
strength,
|
||||
str(layout["affinity"]),
|
||||
"display-only" if display_only else "canonical",
|
||||
"same-layer" if source_layer == target_layer else "",
|
||||
"same-repo" if same_repo else "",
|
||||
)
|
||||
@@ -667,6 +1056,10 @@ def _edge_element(
|
||||
}
|
||||
|
||||
|
||||
def _overlay_data(*sources: object) -> dict[str, Any]:
|
||||
return graph_explorer_overlay_fields(normalize_deployment_overlay(*sources))
|
||||
|
||||
|
||||
def _layout_hints(
|
||||
edge_type: str,
|
||||
source_layer: str,
|
||||
@@ -733,6 +1126,17 @@ def _node_description(kind: str, attributes: object) -> str:
|
||||
)
|
||||
if kind == "BindingAssertion":
|
||||
return str(attributes.get("status", ""))
|
||||
if kind == "RuntimeService":
|
||||
return str(attributes.get("runtime_target_type") or attributes.get("service_type") or "runtime service")
|
||||
if kind == "ApplicationEndpoint":
|
||||
return str(attributes.get("endpoint_url") or attributes.get("domain") or "application endpoint")
|
||||
if kind == "NetworkPort":
|
||||
host = str(attributes.get("host") or "")
|
||||
port = str(attributes.get("port") or "")
|
||||
protocol = str(attributes.get("protocol") or "")
|
||||
return " ".join(part for part in (host, port, protocol) if part)
|
||||
if kind == "DomainName":
|
||||
return str(attributes.get("domain") or "")
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ from typing import Any, Iterable
|
||||
|
||||
from jsonschema import ValidationError
|
||||
|
||||
from .canon import edge_canon_mapping, evidence_state_for, node_canon_mapping
|
||||
from .discovery import (
|
||||
attribute_stable_key,
|
||||
discovery_stable_key,
|
||||
@@ -270,12 +271,22 @@ def project_llm_output(
|
||||
rationale = str(raw_node.get("rationale") or "").strip()
|
||||
if rationale:
|
||||
provenance["rationale"] = rationale
|
||||
canon_mapping = node_canon_mapping(kind)
|
||||
candidates["nodes"].append(
|
||||
{
|
||||
"stable_key": stable_key,
|
||||
"kind": kind,
|
||||
"label": label,
|
||||
"repo": repo_slug,
|
||||
"canon_category": canon_mapping.category,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"evidence_state": evidence_state_for(
|
||||
origin="llm",
|
||||
source_kind="llm",
|
||||
review_state="needs_review",
|
||||
confidence=confidence,
|
||||
),
|
||||
"aliases": _strings(raw_node.get("aliases")) + [label],
|
||||
"attributes": _json_object(raw_node.get("attributes")) if isinstance(raw_node.get("attributes"), dict) else {},
|
||||
"origin": "llm",
|
||||
@@ -304,10 +315,21 @@ def project_llm_output(
|
||||
rationale = str(raw_edge.get("rationale") or "").strip()
|
||||
if rationale:
|
||||
provenance["rationale"] = rationale
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
candidates["edges"].append(
|
||||
{
|
||||
"stable_key": relationship_stable_key(source_key, edge_type, target_key, evidence_scope=scope["id"]),
|
||||
"edge_type": edge_type,
|
||||
"canonical_type": canon_mapping.canonical_type,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"display_only": canon_mapping.display_only,
|
||||
"evidence_state": evidence_state_for(
|
||||
origin="llm",
|
||||
source_kind="llm",
|
||||
review_state="needs_review",
|
||||
confidence=confidence,
|
||||
),
|
||||
"source_key": source_key,
|
||||
"target_key": target_key,
|
||||
"attributes": _json_object(raw_edge.get("attributes")) if isinstance(raw_edge.get("attributes"), dict) else {},
|
||||
|
||||
@@ -16,7 +16,35 @@ ORIGIN_PRECEDENCE = {
|
||||
"manual": 5,
|
||||
}
|
||||
|
||||
PATH_SCOPED_NODE_KINDS = {"lockfile"}
|
||||
PATH_SCOPED_NODE_KINDS = {
|
||||
"container-build",
|
||||
"deployment-service",
|
||||
"domain-name",
|
||||
"kubernetes-config-map",
|
||||
"kubernetes-cron-job",
|
||||
"kubernetes-daemon-set",
|
||||
"kubernetes-deployment",
|
||||
"kubernetes-horizontal-pod-autoscaler",
|
||||
"kubernetes-ingress",
|
||||
"kubernetes-job",
|
||||
"kubernetes-namespace",
|
||||
"kubernetes-secret",
|
||||
"kubernetes-service",
|
||||
"kubernetes-stateful-set",
|
||||
"lockfile",
|
||||
"network-port",
|
||||
"score-workload",
|
||||
"service-config",
|
||||
}
|
||||
EVIDENCE_AGGREGATE_EDGE_TYPES = {
|
||||
"exposes_port",
|
||||
"listens_on",
|
||||
"names_endpoint",
|
||||
"opens_port",
|
||||
"resolves_to",
|
||||
"routes_to_port",
|
||||
"routes_to_service",
|
||||
}
|
||||
|
||||
|
||||
def reconcile_discovery_snapshots(
|
||||
@@ -186,9 +214,12 @@ def _edge_conflicts(by_key: dict[str, dict[str, Any]]) -> list[dict[str, object]
|
||||
conflicts: list[dict[str, object]] = []
|
||||
seen: dict[tuple[str, str, str], str] = {}
|
||||
for key, edge in sorted(by_key.items()):
|
||||
edge_type = str(edge.get("edge_type") or "")
|
||||
if edge_type in EVIDENCE_AGGREGATE_EDGE_TYPES:
|
||||
continue
|
||||
match_key = (
|
||||
str(edge.get("source_key") or ""),
|
||||
str(edge.get("edge_type") or ""),
|
||||
edge_type,
|
||||
str(edge.get("target_key") or ""),
|
||||
)
|
||||
other = seen.get(match_key)
|
||||
@@ -292,11 +323,24 @@ def _path_scoped_nodes_are_distinct(left: dict[str, Any] | None, right: dict[str
|
||||
right_kind = normalize_identity_part(str(right.get("kind") or ""))
|
||||
if left_kind != right_kind or left_kind not in PATH_SCOPED_NODE_KINDS:
|
||||
return False
|
||||
left_identities = _source_anchor_identities(left)
|
||||
right_identities = _source_anchor_identities(right)
|
||||
if left_identities and right_identities:
|
||||
return left_identities.isdisjoint(right_identities)
|
||||
left_paths = _source_anchor_paths(left)
|
||||
right_paths = _source_anchor_paths(right)
|
||||
return bool(left_paths and right_paths and left_paths.isdisjoint(right_paths))
|
||||
|
||||
|
||||
def _source_anchor_identities(candidate: dict[str, Any]) -> set[str]:
|
||||
anchors = candidate.get("source_anchors") if isinstance(candidate.get("source_anchors"), list) else []
|
||||
return {
|
||||
str(anchor.get("fingerprint") or "")
|
||||
for anchor in anchors
|
||||
if isinstance(anchor, dict) and anchor.get("fingerprint")
|
||||
}
|
||||
|
||||
|
||||
def _source_anchor_paths(candidate: dict[str, Any]) -> set[str]:
|
||||
anchors = candidate.get("source_anchors") if isinstance(candidate.get("source_anchors"), list) else []
|
||||
return {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user