Skip to content
UPG is in early alpha. The specification is still evolving and may change between versions.
Tools/Validation/validate_graph

validate_graph

Walk the loaded graph and return a per-class, per-node report of schema drift plus anti-pattern violations from `UPG_ANTI_PATTERNS`. Schema-drift classes: non-canonical entity types, non-canonical edge types, top-level fields outside `UPGBaseNode`, invalid status values, self-referential `source_id`/`source_type`, properties matching `UPG_PROPERTY_MIGRATIONS` rules. Anti-patterns: catalog entries that fired against the live graph, sorted high → medium → low. Each entry carries `suggested_migration` (drift) or `remediation` (anti-pattern). Top-level `valid` is true iff drift is empty AND no violations fired. Read-only; pairs with `migrate_type`, `rename_edge_type`, `get_anti_pattern_violations_for`.

Validationatomic (read-only)

Arguments

scopestringoptional

Which drift class(es) to include in the response (default "all"). Counts in `summary` are always returned for every class.

allentity_driftedge_driftproperty_drifttop_level_driftlifecycle_driftself_referentialconfiguration_drift
limitnumberoptional

Max entries per class (default 100, max 1000)

severitystringoptional

Filter anti-pattern violations to one severity tier.

highmediumlow
anti_pattern_idsarrayoptional

Restrict anti-pattern evaluation to a subset of catalog ids (e.g. ["features-without-hypotheses"]).

skip_driftbooleanoptional

Skip the schema-drift block. Only returns anti-pattern violations.

skip_anti_patternsbooleanoptional

Skip anti-pattern evaluation. Only returns schema drift.

configurationobjectoptional

Evaluate ANTI-PATTERNS against one configuration instead of across the family. Maps a configuration_axis (by node id, or by title when unambiguous) to a single one of its values. Configuration drift is NOT narrowed: the declarations it checks are facts about the whole family, so they are always validated on the union, by design. Omit this and anti-patterns are still evaluated per projection automatically: findings true everywhere report unqualified, findings true in only some report annotated with where they hold, and findings true only of the superposed union are suppressed and counted. Rejected with an error alongside skip_anti_patterns: true, since it would then have no effect.

if_changed_sincestringoptional

Hash from a previous response. Returns { changed: false } if graph unchanged.

include_polymorphic_upgradesbooleanoptional

When true, include a `polymorphic_with_typed_alternative` array listing polymorphic edges (e.g. node_owned_by_person, node_constrains_node) that have a more-specific typed alternative for their actual source/target pair. Opt-in only; omitted by default to avoid cluttering routine validation output. Does not affect `valid`; these are advisory suggestions.

pending_nodesarrayoptional

Batch-4 #18 pre-commit preview: nodes you are ABOUT to create. When supplied (with/without pending_edges), validate_graph evaluates anti-patterns against the CURRENT graph PLUS this delta WITHOUT writing, and returns which violations the delta would newly trigger or resolve. Each item: `{ type, title?, status?, tags?, properties? }`.

pending_edgesarrayoptional

Pre-commit preview edges (paired with pending_nodes). Each item: `{ from, to, type? }`, where from/to is an existing node id OR a `$N` index into pending_nodes; type is inferred from endpoints when omitted.

Returns

Shape
{ valid, structurally_valid?, summary, entity_drift?, edge_drift?, property_drift?, top_level_drift?, lifecycle_drift?, self_referential?, counts_drift?, integrity_drift?, header_seal_note?, anti_pattern_violations?, notes?, _hash }
Per-class drift arrays appear only when the requested scope includes that class.
Each array is capped at limit (default 100).
structurally_valid is omitted when skip_drift: true.

Examples

Live call against the Notion example graph.

Output

{
  "valid": false,
  "summary": {
    "entity_drift": 0,
    "edge_drift": 0,
    "top_level_drift": 0,
    "lifecycle_drift": 0,
    "self_referential": 0,
    "property_drift": 164,
    "total_nodes": 2054,
    "total_edges": 3216,
    "spec_version": "0.7.3",
    "scope": "all",
    "limit": 100,
    "anti_pattern_violations_high": 0,
    "anti_pattern_violations_medium": 1,
    "anti_pattern_violations_low": 0,
    "edge_type_pair_drift": 100,
    "graph_topology_self_loops": 0,
    "property_type_drift": 100
  },
  "_hash": "c0f701e327319f2e",
  "entity_drift": [],
  "edge_drift": [],
  "top_level_drift": [],
  "lifecycle_drift": [],
  "self_referential": [],
  "property_drift": [
    {
      "id": "ec3d5479-4947-4bd9-9e77-b5ee01beb851",
      "type": "product",
      "property": "stage",
      "via": "UPG_PROPERTY_MIGRATIONS['0.2.13']"
    },
    {
      "id": "3f1c1804-b33b-4b1f-9094-c91cc8957912",
      "type": "assumption",
      "property": "validation_status",
      "via": "UPG_PROPERTY_MIGRATIONS['0.5.0']"
    },
    {
      "id": "6719fce7-61e7-44df-b6cf-92f7c7312554",
      "type": "assumption",
      "property": "validation_status",
      "via": "UPG_PROPERTY_MIGRATIONS['0.5.0']"
    },
    {
      "id": "7b22f5f3-b344-4e49-8d5a-35cf9087dc6c",
      "type": "assumption",
      "property": "validation_status",
      "via": "UPG_PROPERTY_MIGRATIONS['0.5.0']"
… (truncated)

Warnings

`valid` is true ONLY when both drift is empty AND no anti-pattern violations fired — it conflates structure and product-health. For a pure spec-conformance check read `structurally_valid` (or set `skip_anti_patterns: true`, which makes `valid` track structure alone). `skip_drift: true` gives a catalog-only run and omits `structurally_valid`.

`counts_drift` / `integrity_drift` describe the `.upg` file ON DISK, not the in-memory graph — they re-read the file and compare its header to its own body. Unsaved writes therefore never show up as seal drift (the last-written file is still self-consistent), and a seal defect is repaired by `upg fmt`, not by any of the `migrate_*` tools.

See Also