Skip to content

I1966 — The composed graph is whole or its generation refuses

Stop docs/generated/graph.json from silently dropping nodes and edges at the MAX_NODES cap. Either the cap is raised or removed under a measured size budget, or generation refuses a truncated graph and names the count and the cap. The committed graph must never be a prefix of the repository that still passes as whole.

DONE wave 0 · p1 · implementation profile · parallel safe

Part of capability-graph — One capability graph, two projections, and no second inventory of what this repository can do.

Objective

Stop docs/generated/graph.json from silently dropping nodes and edges at the MAX_NODES cap. Either the cap is raised or removed under a measured size budget, or generation refuses a truncated graph and names the count and the cap. The committed graph must never be a prefix of the repository that still passes as whole.

Why

graph::Builder::node refuses every node past MAX_NODES = 2000 (apps/majordomus-cli/src/graph.rs). It sets metadata.truncated, and compose() then drops each edge whose end could not be added. generate::graph_document refuses an unresolved relation (ADR 0020, case 390) but writes a truncated graph without a word. derive-check stays green, and every newly indexed object takes the place of a real node and its edges. This is the defect case 390 was written against, 'the file looks whole and is quietly short of one edge', reached by another route. It also breaks the capability-graph milestone's criterion that a relation that does not resolve fails generation rather than going missing.

Current state

The committed docs/generated/graph.json is composed without a node bound (graph::composed_complete, 9f98133aa2, PR #788) and holds 2434 nodes and 5341 edges with metadata.truncated false in 2,766,504 bytes; the implemented_by edge from majordomus://claim/mcp-uri-resolution to apps/majordomus-cli/src/capability/builtin/objects.rs is present. graph::whole refuses a truncated graph committed as data, and the site's why and product graphs go through it, proved by unit tests in graph.rs. Still open: no case drives generation over a fixture past a bound, because the composed graph has none, and no measured size budget is written beside MAX_NODES (2000, the bound of a graph a person is shown). Measured on master 4090b2b8ae, 2026-10-08.

Desired state

A committed graph.json is either complete, with metadata.truncated false, or it is not written: generation fails, naming the node count it needed and the cap it hit, as graph_document already does for an unresolved relation. If the cap is raised or removed for the committed artifact, the new bound is a size budget measured on this repository and written beside the constant with its measurement. The Cockpit and the interactive views may keep a display bound of their own, and they keep saying when they apply it.

Scope

  • apps/majordomus-cli/src/graph.rs
  • apps/majordomus-cli/src/generate.rs
  • test/cases/390_unresolved_relation_refuses_generation.sh
  • docs/generated/graph.json
  • docs/generated/graph.schema.json

Out of scope

  • The mesh registry's own MAX_NODES (apps/majordomus-cli/src/mesh/registry.rs), which is an unrelated bound on peers
  • The Cockpit's graph pages and the site's why-graph and product-graph, apart from keeping their existing truncation notice true
  • Changing which objects, claims or relations are composed: the inputs stay as they are, and only the completeness of the output is at stake
  • Pruning claims or index classes to get under the cap

Dependencies

None. This issue is a root of the graph.

Acceptance criteria

  • Generating the composed graph over this repository yields metadata.truncated false, or generation exits non-zero naming the node count and the cap
  • The implemented_by edge from majordomus://claim/mcp-uri-resolution to apps/majordomus-cli/src/capability/builtin/objects.rs is present in docs/generated/graph.json, and so is every claim edge whose target file exists
  • A fixture whose composition exceeds the bound makes generation refuse; it does not write a truncated graph.json, and the case fails if the refusal is removed
  • Any new bound on the committed artifact has its measured size (bytes, nodes, edges) written beside the constant

Validation

  • bash test/run.sh 390_unresolved_relation_refuses_generation
  • cargo test --lib graph::
  • scripts/derive-check

Evidence required

  • composed_graph_complete_or_refused
  • truncation_refusal_case
  • size_budget_measured

Evidence

coverstypecommandresultat commit
composed_graph_complete_or_refusedtestjq '.graph.metadata.truncated, (.graph.nodes|length), (.graph.edges|length)' docs/generated/graph.json; cargo test --lib -- graph:: generate::tests::a_composed_graphok at e8505a431d plus this change, on lundra: 89 passed, 0 failed. The committed docs/generated/graph.json holds 2795 nodes and 6128 edges with metadata.truncated false, past MAX_NODES 2000, because graph::composed_complete composes without a bound; generation now passes it through graph::whole, so a truncated composition exits 10 naming the count it held and the bound instead of being written. The implemented_by edge from majordomus://claim/mcp-uri-resolution to apps/majordomus-cli/src/capability/builtin/objects.rs is presente8505a4
truncation_refusal_casetestcargo test --lib generate::tests::a_composed_graph_that_stopped_at_a_bound_is_refused_and_not_committed, then again with the refusal in graph::whole removedok as written: a composition bounded at one node is refused with exit 10 and a message naming 1 node and the bound 2000, and a whole one is accepted. With the refusal removed the same test FAILED, panicked at generate.rs:3248, so the test fails if the refusal is removed; the file was restored afterwardse8505a4
size_budget_measuredartifactwc -c docs/generated/graph.json; jq '(.graph.nodes|length), (.graph.edges|length)' docs/generated/graph.jsonmeasured at master 97c4521f91 and written beside graph::composed_complete: 2795 nodes, 6128 edges, 3,339,236 bytes. The committed artifact carries no bound, so no new constant was introduced; MAX_NODES stays the bound of a graph a person is showne8505a4

Risk

Raising the cap without a budget brings the problem back one size later, and graph.json is already 2.3 MB. Refusing outright makes every derive fail until the bound is settled, so the refusal and the new bound belong in one change.

Timeline

started
2026-10-14T20:13:37Z
verified
2026-10-14T20:14:49Z
completed
2026-10-14T20:15:20Z

Those three fields, the evidence above and the state of the dependencies are all the status is made of. There is no status field to disagree with them.

Canonical record: .ai/repo/project/issues/I1966.yaml. Read it back with majordomus plan show I1966.

Searches the titles and descriptions of every page this site publishes.