Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions AI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ The response always includes:
### Supported phase-1 entry kinds
- `Pod`
- `Workload`
- `PVC`
- `PV`
- `StorageClass`
- `CSIDriver`

## Response shape

Expand All @@ -46,6 +50,20 @@ The response always includes:
"edges": [...],
"collectedAt": "...",
"explanation": [...],
"warnings": [],
"partial": false,
"degradedSources": [],
"budgets": {
"maxDepth": 2,
"storageMaxDepth": 5,
"maxNodes": 200,
"maxEdges": 400,
"nodeCount": 12,
"edgeCount": 18,
"truncated": false
},
"rankedEvidence": [],
"conflicts": [],
"nodeCount": 12,
"edgeCount": 18,
"freshness": {
Expand All @@ -64,6 +82,22 @@ may ignore them. Agents should use `freshness.ready`, `freshness.lastRefreshAt`,
and graph counts to decide whether to retry, warn the user, or proceed with
best-effort reasoning.

`warnings`, `partial`, `degradedSources`, `budgets`, `rankedEvidence`, and
`conflicts` are additive diagnostic metadata. Agents should read them before
forming conclusions:

- `partial=true` means the graph was intentionally truncated or collected from
degraded inputs. Do not claim completeness.
- `budgets.truncated=true` names concrete limits through
`budgets.truncationReasons`.
- `warnings` contains machine-readable caveats and next actions when known.
- `degradedSources` describes Kubernetes sources that were forbidden,
unavailable, or partial.
- `rankedEvidence` provides ordered evidence, currently starting with Event
evidence derived from the returned graph slice.
- `conflicts` preserves contradictory evidence instead of selecting a silent
winner.

Server error responses keep the historical `error` string and add structured
fields:

Expand Down Expand Up @@ -205,6 +239,15 @@ This means a response may go deeper along storage edges than along unrelated top

Agents should not assume a single uniform BFS depth across all returned paths.

### `maxNodes` and `maxEdges`
Hard caps on the returned diagnostic graph slice. When either cap is hit, the
response sets `partial=true`, `budgets.truncated=true`, and includes a
`diagnostic_budget_exceeded` warning.

The default caps are intentionally conservative for in-memory operation. Agents
may ask the user to narrow namespace/depth first, then raise caps only when the
larger graph is needed.

### `terminalNodeKinds`
Boundary node kinds that are included in the response but are not used as
another traversal frontier.
Expand Down Expand Up @@ -245,6 +288,14 @@ For example:

It should not be used as the only signal for graph facts already present in nodes/edges.

### Step 5: Honor partial and ranked evidence fields
Before writing a diagnosis:

- check `partial`, `warnings`, and `degradedSources`
- inspect `rankedEvidence` before unranked `explanation`
- mention `conflicts` directly instead of resolving them silently
- include budget truncation in the answer when `budgets.truncated=true`

## Phase-1 examples of valid AI conclusions

### Safe conclusions
Expand Down Expand Up @@ -318,6 +369,20 @@ Diagnose a pod or workload:
./bin/kubernetes-ontology --server "http://127.0.0.1:18080" --diagnose-workload --namespace default --name my-deployment
```

Constrain or expand diagnostic graph budgets explicitly:

```bash
./bin/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--diagnose-pod \
--namespace default \
--name my-pod \
--max-depth 2 \
--storage-max-depth 5 \
--max-nodes 200 \
--max-edges 400
```

List filtered relations:

```bash
Expand All @@ -337,5 +402,7 @@ Post-MVP backend changes must preserve:
- provenance meaning
- policy meaning
- top-level response shape
- additive diagnostic metadata semantics for `partial`, `warnings`, `budgets`,
`rankedEvidence`, `degradedSources`, and `conflicts`

If any of those change, downstream AI consumers should treat it as a contract version change.
11 changes: 7 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ WORKLOAD_RESOURCES ?=
CONTROLLER_RULES ?=
MAX_DEPTH ?= 2
STORAGE_MAX_DEPTH ?= 5
MAX_NODES ?=
MAX_EDGES ?=
BOOTSTRAP_TIMEOUT ?= 2m
OBSERVE_DURATION ?= 40s
POLL_INTERVAL ?= 2s
Expand All @@ -36,6 +38,7 @@ EXPAND_DEPTH ?= 1
OVERRIDE_ORIGINS := command line environment override
make_arg = $(if $(filter $(OVERRIDE_ORIGINS),$(origin $(1))),--$(2) "$($(1))")
make_context_namespaces_arg = $(if $(filter $(OVERRIDE_ORIGINS),$(origin CONTEXT_NAMESPACES) $(origin NAMESPACES)),--context-namespaces "$(CONTEXT_NAMESPACES)")
make_diagnostic_budget_args = $(call make_arg,MAX_NODES,max-nodes) $(call make_arg,MAX_EDGES,max-edges)
CLI_CONFIG_OVERRIDES = $(call make_arg,KUBECONFIG,kubeconfig) $(call make_arg,CLUSTER,cluster) $(make_context_namespaces_arg) $(call make_arg,WORKLOAD_RESOURCES,workload-resources) $(call make_arg,CONTROLLER_RULES,controller-rules) $(call make_arg,BOOTSTRAP_TIMEOUT,bootstrap-timeout)
DAEMON_CONFIG_OVERRIDES = $(CLI_CONFIG_OVERRIDES) $(call make_arg,SERVER_ADDR,addr) $(call make_arg,POLL_INTERVAL,poll-interval)
CLI_CONFIG_ARGS = $(if $(CONFIG),--config "$(CONFIG)" $(CLI_CONFIG_OVERRIDES),--kubeconfig "$(KUBECONFIG)" --cluster "$(CLUSTER)" --context-namespaces "$(CONTEXT_NAMESPACES)" --workload-resources "$(WORKLOAD_RESOURCES)" --controller-rules "$(CONTROLLER_RULES)" --bootstrap-timeout "$(BOOTSTRAP_TIMEOUT)")
Expand Down Expand Up @@ -160,7 +163,7 @@ diagnose-pod: build require-kubeconfig require-entry
--namespace "$(NAMESPACE)" \
--name "$(NAME)" \
--max-depth "$(MAX_DEPTH)" \
--storage-max-depth "$(STORAGE_MAX_DEPTH)"
--storage-max-depth "$(STORAGE_MAX_DEPTH)" $(make_diagnostic_budget_args)

diagnose-workload: build require-kubeconfig require-entry
$(BINARY) \
Expand All @@ -169,7 +172,7 @@ diagnose-workload: build require-kubeconfig require-entry
--namespace "$(NAMESPACE)" \
--name "$(NAME)" \
--max-depth "$(MAX_DEPTH)" \
--storage-max-depth "$(STORAGE_MAX_DEPTH)"
--storage-max-depth "$(STORAGE_MAX_DEPTH)" $(make_diagnostic_budget_args)

diagnose-pod-server: build require-entry
$(BINARY) \
Expand All @@ -178,7 +181,7 @@ diagnose-pod-server: build require-entry
--namespace "$(NAMESPACE)" \
--name "$(NAME)" \
--max-depth "$(MAX_DEPTH)" \
--storage-max-depth "$(STORAGE_MAX_DEPTH)"
--storage-max-depth "$(STORAGE_MAX_DEPTH)" $(make_diagnostic_budget_args)

diagnose-workload-server: build require-entry
$(BINARY) \
Expand All @@ -187,7 +190,7 @@ diagnose-workload-server: build require-entry
--namespace "$(NAMESPACE)" \
--name "$(NAME)" \
--max-depth "$(MAX_DEPTH)" \
--storage-max-depth "$(STORAGE_MAX_DEPTH)"
--storage-max-depth "$(STORAGE_MAX_DEPTH)" $(make_diagnostic_budget_args)

visualize:
@echo "Starting ontology viewer on http://$(VIEWER_HOST):$(VIEWER_PORT)"
Expand Down
16 changes: 12 additions & 4 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,9 @@ go run ./cmd/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--diagnose-pod \
--namespace default \
--name my-pod
--name my-pod \
--max-nodes 200 \
--max-edges 400
```

For workloads:
Expand All @@ -546,12 +548,16 @@ For workloads:

The diagnostic response is a focused subgraph intended for the MVP
fault-diagnosis workflow and downstream AI-agent consumption.
It includes additive `partial`, `warnings`, `budgets`, `rankedEvidence`,
`degradedSources`, and `conflicts` fields so agents can tell bounded evidence
from complete cluster truth.

Pod-centered diagnostic queries keep shared nodes bounded by default. For
example, a pod's `ServiceAccount` is shown, but the traversal does not continue
through that ServiceAccount to every other pod using it. Use
`terminalKinds=...` or `expandTerminalNodes=true` on HTTP queries when you need
that deeper fan-out.
that deeper fan-out. Use `maxNodes` and `maxEdges` when you need an explicit
response budget.

### Use HTTP Directly

Expand All @@ -563,13 +569,14 @@ curl -s 'http://127.0.0.1:18080/entities?kind=Pod&namespace=default&limit=20'
curl -s 'http://127.0.0.1:18080/entity?kind=Pod&namespace=default&name=my-pod'
curl -s 'http://127.0.0.1:18080/neighbors?entityGlobalId=your/entityGlobalId&direction=out'
curl -s 'http://127.0.0.1:18080/expand?entityGlobalId=your/entityGlobalId&depth=1'
curl -s 'http://127.0.0.1:18080/diagnostic/pod?namespace=default&name=my-pod'
curl -s 'http://127.0.0.1:18080/diagnostic/pod?namespace=default&name=my-pod&maxNodes=200&maxEdges=400'
curl -s 'http://127.0.0.1:18080/diagnostic/pod?namespace=default&name=my-pod&expandTerminalNodes=true'
```

Graph and list responses include the original fields plus additive `freshness`
metadata from the daemon runtime status. Error responses include the historical
`error` string plus `code`, `message`, `status`, `retryable`, and `source`.
Diagnostic responses additionally include budget and ranked-evidence metadata.

For agent workflows that need machine-readable stderr on failures:

Expand Down Expand Up @@ -599,7 +606,8 @@ http://127.0.0.1:8765

Click `Load topology` to read live entities and relations from `SERVER_URL`.
Use `Auto refresh` for continuous polling, or load a focused pod/workload
diagnostic graph from the same page.
diagnostic graph from the same page. The Diagnostic Signals panel surfaces
budget truncation, warnings, conflicts, degraded sources, and ranked evidence.

Select a node and use `Expand 1 hop` to fetch the next layer from the daemon.
The CLI equivalent is:
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,15 @@ Diagnose a pod:
--server "http://127.0.0.1:18080" \
--diagnose-pod \
--namespace default \
--name my-pod
--name my-pod \
--max-nodes 200 \
--max-edges 400
```

Diagnostic responses include additive `partial`, `warnings`, `budgets`,
`rankedEvidence`, `degradedSources`, and `conflicts` fields. Agents should use
those fields to distinguish bounded evidence from complete cluster truth.

Expand one graph node:

```bash
Expand Down Expand Up @@ -426,12 +432,14 @@ The daemon exposes the current in-memory ontology database over HTTP:
- `GET /relations?from=...&kind=scheduled_on`
- `GET /neighbors?entityGlobalId=...&direction=out`
- `GET /expand?entityGlobalId=...&depth=1`
- `GET /diagnostic/pod?namespace=default&name=my-pod`
- `GET /diagnostic/pod?namespace=default&name=my-pod&maxNodes=200&maxEdges=400`
- `GET /diagnostic/workload?namespace=default&name=my-deployment`

Graph and list responses include additive `freshness` metadata when daemon
runtime status is available. Error responses include `code`, `message`,
`status`, `retryable`, and `source` alongside the historical `error` string.
Diagnostic responses additionally include explicit partial/budget metadata and
ranked evidence for downstream agents.

## Visualization

Expand All @@ -457,7 +465,9 @@ Open `http://127.0.0.1:8765`.

The viewer can load live topology, query focused diagnostic graphs, expand and
collapse nodes, filter by node or relation metadata, inspect provenance, and
export the visible subgraph as JSON.
export the visible subgraph as JSON. Focused diagnostic graphs show a
Diagnostic Signals panel with budget truncation, warnings, conflicts, degraded
sources, and ranked evidence before lower-level explanation text.

## Architecture

Expand Down Expand Up @@ -532,7 +542,8 @@ archives, so marketplace pages should link to the live repository path:
open-source MVP.
- RBAC topology is represented for ServiceAccount subjects and binding objects;
it is not a full permission reasoning engine.
- Evidence ranking is basic.
- Evidence ranking currently starts with returned Event evidence and will grow
into richer signal ranking over time.
- RDF/OWL materialization is not implemented.

## Roadmap
Expand Down
16 changes: 12 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,15 @@ OpenKruise,这是正常情况,不需要为此中断启动。
--server "http://127.0.0.1:18080" \
--diagnose-pod \
--namespace default \
--name my-pod
--name my-pod \
--max-nodes 200 \
--max-edges 400
```

诊断返回会额外包含 `partial`、`warnings`、`budgets`、
`rankedEvidence`、`degradedSources` 和 `conflicts`。Agent 应优先读取
这些字段,区分“有边界的证据图”和“完整集群事实”。

展开一个图节点:

```bash
Expand All @@ -328,16 +334,18 @@ daemon 暴露只读 HTTP API:
- `GET /relations?from=...&kind=scheduled_on`
- `GET /neighbors?entityGlobalId=...&direction=out`
- `GET /expand?entityGlobalId=...&depth=1`
- `GET /diagnostic/pod?namespace=default&name=my-pod`
- `GET /diagnostic/pod?namespace=default&name=my-pod&maxNodes=200&maxEdges=400`
- `GET /diagnostic/workload?namespace=default&name=my-deployment`

返回结果会尽量带上 `freshness` 元数据,帮助调用方判断图数据是否 ready、最后
一次刷新是什么时候。
一次刷新是什么时候。诊断结果还会带上 partial/budget 元数据和 ranked
evidence,方便下游 Agent 做安全推理。

## 可视化

本地拓扑 viewer 可以展示 live topology、诊断子图、节点详情、边来源和导出
JSON。
JSON。诊断子图会在 Diagnostic Signals 面板里优先展示预算截断、warning、
conflict、degraded source 和 ranked evidence。

启动 daemon 后运行:

Expand Down
6 changes: 6 additions & 0 deletions cmd/kubernetes-ontology-viewer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ func (h *handler) serveDiagnostic(w http.ResponseWriter, r *http.Request) {
params.Set("name", name)
params.Set("maxDepth", first(q, "maxDepth", "2"))
params.Set("storageMaxDepth", first(q, "storageMaxDepth", "5"))
if maxNodes := first(q, "maxNodes", ""); maxNodes != "" {
params.Set("maxNodes", maxNodes)
}
if maxEdges := first(q, "maxEdges", ""); maxEdges != "" {
params.Set("maxEdges", maxEdges)
}
if terminalKinds := first(q, "terminalKinds", ""); terminalKinds != "" {
params.Set("terminalKinds", terminalKinds)
}
Expand Down
16 changes: 16 additions & 0 deletions cmd/kubernetes-ontology/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func main() {
var terminalKindsRaw string
var maxDepth int
var storageMaxDepth int
var maxNodes int
var maxEdges int
var expandTerminalNodes bool
var bootstrapTimeout time.Duration
var statusOnly bool
Expand Down Expand Up @@ -82,6 +84,8 @@ func main() {
flag.StringVar(&terminalKindsRaw, "terminal-kinds", "", "Comma-separated diagnostic terminal node kinds. Empty uses defaults; 'none' disables terminal boundaries.")
flag.IntVar(&maxDepth, "max-depth", 2, "Maximum general BFS depth for diagnostic subgraph traversal")
flag.IntVar(&storageMaxDepth, "storage-max-depth", 5, "Maximum BFS depth for storage and CSI related traversal")
flag.IntVar(&maxNodes, "max-nodes", 0, "Maximum diagnostic nodes to return. Empty uses the built-in safe default.")
flag.IntVar(&maxEdges, "max-edges", 0, "Maximum diagnostic edges to return. Empty uses the built-in safe default.")
flag.BoolVar(&expandTerminalNodes, "expand-terminal-nodes", false, "Traverse through diagnostic terminal nodes instead of stopping at them")
flag.DurationVar(&bootstrapTimeout, "bootstrap-timeout", 2*time.Minute, "Timeout for initial full snapshot bootstrap")
flag.BoolVar(&statusOnly, "status-only", false, "Bootstrap runtime and print runtime status instead of querying a diagnostic subgraph")
Expand Down Expand Up @@ -168,6 +172,8 @@ func main() {
name: name,
maxDepth: maxDepth,
storageMaxDepth: storageMaxDepth,
maxNodes: maxNodes,
maxEdges: maxEdges,
terminalNodeKinds: terminalNodeKinds,
expandTerminalNodes: expandTerminalNodes,
getEntity: getEntity,
Expand Down Expand Up @@ -305,6 +311,8 @@ func main() {
result, err := manager.Facade().QueryDiagnosticSubgraph(entryKind, namespace, name, query.DiagnosticOptions{
MaxDepth: maxDepth,
StorageMaxDepth: storageMaxDepth,
MaxNodes: maxNodes,
MaxEdges: maxEdges,
TerminalNodeKinds: terminalNodeKinds,
ExpandTerminalNodes: expandTerminalNodes,
})
Expand Down Expand Up @@ -471,6 +479,8 @@ type serverQueryOptions struct {
name string
maxDepth int
storageMaxDepth int
maxNodes int
maxEdges int
terminalNodeKinds []api.NodeKind
expandTerminalNodes bool
getEntity bool
Expand Down Expand Up @@ -539,6 +549,12 @@ func queryServer(server string, options serverQueryOptions) error {
if options.storageMaxDepth > 0 {
values.Set("storageMaxDepth", strconv.Itoa(options.storageMaxDepth))
}
if options.maxNodes > 0 {
values.Set("maxNodes", strconv.Itoa(options.maxNodes))
}
if options.maxEdges > 0 {
values.Set("maxEdges", strconv.Itoa(options.maxEdges))
}
if len(options.terminalNodeKinds) > 0 {
values.Set("terminalKinds", joinNodeKinds(options.terminalNodeKinds))
}
Expand Down
Loading
Loading