Graph Metadata Editor

Status: design contract.

The editor works on graph claims, not raw file formats. A human or agent edits the same conceptual object everywhere:

kind
nodes
summary
links
backing source
validation issues

The backing writer decides whether the change eventually lands in Markdown frontmatter, .graph.ts, an inline @graph block, QA suite YAML, test catalog metadata, or an Ops DB row.

Non-Negotiables

  • API first, MCP second, UI third.
  • No raw YAML/TypeScript editing in the normal UI.
  • No required "preview patch" or "commit metadata" step in the editor.
  • The editor saves through the owning API.
  • A later Git flush path materializes file-backed overlays into source files.
  • Domain validators stay domain-specific. Only the actor policy gate is shared.

Claim Shape

interface GraphClaim {
  id: string;
  kind: 'idea' | 'behaviour' | 'implementation' | 'suite' | 'test' | 'journal' | 'infra' | 'anomaly' | 'event';
  nodes: string[];
  summary?: string;
  links: Partial<Record<GraphClaim['kind'], string[]>>;
  backing: {
    type: 'markdown-frontmatter' | 'graph-ts' | 'inline-graph-block' | 'suite-yaml' | 'test-catalog' | 'ops-db';
    path?: string;
    table?: string;
    recordId?: string;
    sourceHash?: string;
  };
  issues: GraphIssue[];
}

interface GraphIssue {
  level: 'hard' | 'soft' | 'info';
  code: string;
  message: string;
  path?: string;
}

Actor Policy Gate

Validators emit issues. The shared gate decides whether the actor may save.

Actor Soft issues Hard issues
human Save allowed; UI shows warning. Reject.
agent Reject unless force: true; forced save is audited. Reject.
system / pipeline Reject. Reject.

This is the same policy shape as CVL selection cardinality: humans can leave honest invalid state, agents can deliberately force soft-rule exceptions, and less intelligent machinery must keep invariants clean. Hard gates protect parseability, source safety, and known hub identity for everyone.

Validation Levels

Hard:

  • unknown hub kind;
  • unparseable backing source;
  • malformed node id that cannot roundtrip;
  • writer cannot serialize without corrupting the source;
  • path/table/record target is outside the allowed backing set.

Soft:

  • missing required graph link;
  • dangling target that could be created later;
  • source-backed grounding gap;
  • duplicate link or duplicate node after normalization.

Info:

  • suggested link target;
  • stale summary;
  • generated claim that should be promoted to an owning source.

API Contract

The API owns the write path. The UI does not know whether it is editing Markdown, TypeScript, YAML, or a DB record.

GET   /v1/graph/claims
GET   /v1/graph/claims/:claimId
PATCH /v1/graph/claims/:claimId
POST  /v1/graph/claims/:claimId/validate
GET   /v1/graph/metadata/flush/status
POST  /v1/graph/metadata/flush

PATCH /v1/graph/claims/:claimId receives:

interface UpdateGraphClaimRequest {
  actorClass: 'human' | 'agent' | 'system' | 'pipeline';
  actorId?: string;
  force?: boolean;
  baseSourceHash?: string;
  patch: {
    kind?: GraphClaim['kind'];
    nodes?: string[];
    summary?: string | null;
    links?: Partial<Record<GraphClaim['kind'], string[]>>;
  };
}

The response always includes the policy decision:

interface UpdateGraphClaimResponse {
  claim: GraphClaim;
  accepted: boolean;
  warnings: GraphIssue[];
  errors: GraphIssue[];
  persisted: 'overlay' | 'db-record' | 'source-file';
  flushState?: 'not-needed' | 'pending' | 'flushed' | 'conflict';
}

For file-backed objects, the online API should write an overlay row first. The live graph projection reads source claim + overlay. A flush worker materializes overlays into Git-backed files and marks the overlay flushed only after the source write succeeds.

MCP Contract

MCP mirrors the API. Tool names should be boring and direct:

list_graph_claims
get_graph_claim
update_graph_claim
validate_graph_claim
flush_graph_metadata

update_graph_claim takes the same actor envelope and force flag as the API. Agent calls without force must obey soft graph rules. Agent calls with force: true may save soft violations and must create audit/event evidence. MCP must not expose a separate shape or bypass the API's policy gate.

flush_graph_metadata is an operator/tooling action, not an editor button. It is useful for agents and scheduled jobs that materialize pending overlays into Git-backed files.

Writer Registry

Each writer implements the same small interface:

interface GraphClaimWriter {
  read(ref: BackingRef): Promise<GraphClaim>;
  validate(claim: GraphClaim): Promise<GraphIssue[]>;
  saveOverlay(claim: GraphClaim, actor: ActorEnvelope): Promise<GraphClaim>;
  flush?(claim: GraphClaim): Promise<FlushResult>;
}

Initial writers:

Writer Owns
markdown-frontmatter Wiki idea, journal, infra, and Markdown-backed behaviour notes.
graph-ts Codebook implementation claims.
inline-graph-block Small code-side @graph blocks.
suite-yaml QA suite metadata and links.
test-catalog Test metadata and suite/checks links.
ops-db Ops ops_events and ops_anomalies; later mirrored by JobCache-owned Ops tables.

UI Placement

  • Wiki page detail: Markdown-backed idea, journal, infra.
  • Codebook detail: code-backed implementation claims.
  • QA behaviour/suite/test detail: behaviour, suite, test.
  • Ops anomaly/event pages: DB-backed anomaly, event.

All surfaces use the same Graph Metadata Panel and the same API/MCP contract. Only the backing writer changes.

Source: wiki/content/architecture/GRAPH-METADATA-EDITOR.md