Document graph explorer adapter boundary

This commit is contained in:
2026-05-19 01:27:53 +02:00
parent 6fa1d1d824
commit 5777c283f7
6 changed files with 82 additions and 8 deletions

View File

@@ -31,6 +31,19 @@ views are stored in browser `localStorage`, URL query parameters carry normal
filter state, and copied state URLs include a state blob so manual overrides can
be shared without a profile service.
The refined rule panel is also browser-local. Rules target either nodes or
edges, optionally narrow by type and by attributes present on the currently
available elements, and then apply one of five modifiers:
- `show`, `hide`, and `blur` are visibility states.
- `highlight` keeps an element visible and adds visual emphasis.
- `remove` hides matching elements and excludes them from the next layout run.
Rules are applied top to bottom. Later matching rules refine earlier matching
rules. Manual overrides win after rules except for `remove`, which is treated as
stronger because it changes the layout membership. Edges connected to hidden
nodes are hidden; edges connected to removed nodes are removed.
## Refresh
Refresh this checkout into the running registry:
@@ -69,8 +82,8 @@ The automated coverage for this slice lives in `tests/test_graph_explorer.py`:
- registered-only repo projection
- `railiance-fabric export --format graph-explorer`
- registry API routes for manifest, payload, and UI
- static UI wiring for profile controls, orientation text, and shared-state
support
- static UI wiring for profile controls, rule controls, orientation text, and
shared-state support
Manual smoke checks for a local registry:
@@ -108,7 +121,8 @@ The extracted repository should own:
- `GraphExplorerManifest` and `GraphExplorerPayload` schemas
- static graph explorer assets and mount/bootstrap code
- display-state evaluation helpers for hosts that want client-side rules
- display-state and rule evaluation helpers for hosts that want client-side
rules, including `show`, `hide`, `blur`, `highlight`, and `remove`
- browser-local profile handling, URL state, copied state blobs, and profile UI
- Cytoscape rendering, layouts, hover popups, detail panels, focus modes, and
manual override controls
@@ -117,11 +131,57 @@ The extracted repository should own:
Host repositories should keep:
- graph projection and domain metadata enrichment
- host vocabulary and manifest labels for node types, edge types, rule fields,
modes, and orientation terms
- host-side profile persistence, when shared/team profiles are required
- authentication, authorization, and deployment
- domain-specific modes and deep links
- registry or analysis service APIs
## Adapter Readiness Notes
The refined shell is closer to extraction, but these Fabric-specific assumptions
should be made manifest-driven or host-adapted before `graph-explorer-engine`
becomes a separate repo:
- The shell still expects the internal element type field to be named `layer`.
User-facing text now says nodes and node types, but the shared contract should
either rename this to `nodeType` or declare the field through
`manifest.identity`.
- Node shapes are hardcoded against Fabric node type ids such as `repository`,
`service`, `deployment`, `server`, `capability`, `interface`, `dependency`,
`binding`, and `library`.
- Rule-builder attributes are derived from a fixed allowlist. Repo-scoping can
use the model, but the allowlist should move to manifest filter fields so a
host can expose facts, evidence, confidence, freshness, scope, ability, or
other domain attributes without changing engine code.
- Mode behavior is hardcoded for Fabric's `onboarding-gaps`, `unresolved`,
`selected-path`, and `neighborhood` semantics. The reusable engine should
either provide generic selectors or let hosts define mode predicates.
- The orientation panel still contains Fabric-specific renderers for
repositories, services, interfaces, dependencies, and capabilities. This
should stay host-owned or become a manifest-provided details adapter.
- The default detail rows know about `source`, `target`, `edgeType`, `strength`,
deep links, and source references. That is acceptable as a shared baseline,
but host-specific row ordering should be manifest-driven.
The current rule-panel data model is compatible with repo-scoping if repo-scoping
maps its graph elements into the same basic shape:
```json
{
"target": "node",
"type": "fact",
"attribute": "reviewState",
"value": "candidate",
"action": "blur"
}
```
For extraction, prefer repo-scoping adapter parity as the next workplan. One
more Fabric-side polish pass is still useful for orientation workflows, but it
does not need to block proving the second host.
Suggested public API:
```ts

View File

@@ -6,7 +6,7 @@ from typing import Any
from urllib.parse import urlparse
DISPLAY_STATES = ("show", "blur", "hide")
DISPLAY_STATES = ("show", "blur", "hide", "highlight", "remove")
LAYER_ORDER = (
"repository",
"server",
@@ -377,8 +377,13 @@ def fabric_graph_explorer_payload(
"rules": [],
"manual_overrides": {},
"orphaned_overrides": [],
"precedence": "later rules override earlier rules; manual overrides win last",
"connected_edge_behavior": "edges connected to hidden nodes are hidden",
"precedence": (
"later rules override earlier rules; remove excludes matching elements "
"from layout and wins over manual overrides; otherwise manual overrides win last"
),
"connected_edge_behavior": (
"edges connected to hidden nodes are hidden; edges connected to removed nodes are removed"
),
},
"elements": elements,
"hidden_elements": [],

View File

@@ -136,6 +136,8 @@ properties:
- show
- blur
- hide
- highlight
- remove
fields:
type: array
items:

View File

@@ -208,6 +208,8 @@ $defs:
- show
- blur
- hide
- highlight
- remove
profile:
type: object
additionalProperties: true

View File

@@ -41,6 +41,7 @@ def test_graph_explorer_manifest_and_payload_validate() -> None:
assert manifest["profile_persistence"] == "local"
assert manifest["shareable_state"]["profile_id"] is True
assert set(manifest["filter"]["actions"]) >= {"show", "hide", "blur", "highlight", "remove"}
assert {layer["id"] for layer in manifest["layers"]} >= {"server", "deployment"}
filter_labels = {field["id"]: field["label"] for field in manifest["filter"]["fields"]}
assert filter_labels["layer"] == "Node Type"
@@ -68,6 +69,7 @@ def test_graph_explorer_manifest_and_payload_validate() -> None:
assert payload["metrics"]["server_node_count"] >= 1
assert payload["metrics"]["registered_repo_count"] == 2
assert payload["metrics"]["unresolved_count"] == 0
assert "removed nodes are removed" in payload["filter"]["connected_edge_behavior"]
def test_cli_exports_graph_explorer_payload(capsys) -> None:
@@ -101,7 +103,10 @@ def test_graph_explorer_payload_accepts_repo_scoping_shape() -> None:
"manual_overrides": {"feature:1": "show"},
},
"filter": {
"rules": [],
"rules": [
{"action": "highlight", "match": {"layer": "fact", "reviewState": "candidate"}},
{"action": "remove", "match": {"layer": "stale_fact"}},
],
"manual_overrides": {},
"orphaned_overrides": [],
},

View File

@@ -202,7 +202,7 @@ Acceptance notes:
```task
id: RAIL-FAB-WP-0009-T06
status: todo
status: done
priority: medium
state_hub_task_id: "934ea4d9-0d36-414d-9ed7-10f39410da8d"
```