name: graph-mutation-plan description: Cookbook for composing an apply_graph_mutations plan — stable entity_key patterns, the canonical label/edge vocabulary, evidence/invalidation/confidence discipline, and a worked example. Load this when building a non-trivial mutation plan. version: "1.0.0" tags: [mutation, graph, ontology, reference]
Composing an apply_graph_mutations plan
Call apply_graph_mutations(plan, event_id, summary) once per logical group of
mutations. The plan is idempotent on stable entity_keys, so a retried plan
converges rather than duplicating. Every structural mutation must stay inside
the given pot_id — never reference another pot.
Plan fields
summary: one-line description of what this plan records.episodes:{name, episode_body, source_description, reference_time?}— narrative/source text to retain (e.g. a PR body, a note).entity_upserts:{entity_key, labels, properties}.edge_upserts:{edge_type, from_entity_key, to_entity_key, properties}.edge_deletes:{edge_type, from_entity_key, to_entity_key}.invalidations:{reason, target_entity_key?, edge_type?, from_entity_key?, to_entity_key?}.evidence:{kind, ref, metadata?}— what grounds this plan.confidence: float | null.warnings: list[str] — anything you could not confirm.
Stable entity_key cookbook
Use these so re-ingestion (and a later live webhook for a backfilled artifact) upserts the same node:
| Artifact | Key pattern |
|---|---|
| Repository | github:repo:<owner>/<repo> |
| Pull request | github:pr:<owner>/<repo>:<n> |
| Issue | github:issue:<owner>/<repo>:<n> |
| Module / package | module:<repo>:<dotted.path> |
| Feature | feature:<repo>:<slug> |
| Linear issue | linear:issue:<identifier> (e.g. linear:issue:ENG-123) |
| Linear project | linear:project:<id> |
| Linear document | linear:document:<id> |
| Activity (timeline) | timeline:activity:<verb>:<short_hex> |
| Period bucket | timeline:period:daily:<pot_id>:<YYYY-MM-DD> |
When no pattern fits, mint a deterministic key from stable identifiers in the source (never a random id, never a timestamp), so the same fact re-keys the same way.
Canonical vocabulary
Always give an entity at least one canonical label — never only generic
Entity. Labels/edges outside the canonical vocabulary are downgraded
automatically (entities → Document / Observation, edges → RELATED_TO), so
prefer a canonical type when one fits.
Entity labels (topology — source of truth is domain/ontology.py):
Repository, Service, Environment, DataStore, Cluster, Team, Person. Plus the
work/knowledge types the playbooks reference: Activity, Feature, Decision, Fix,
BugPattern, Incident, DiagnosticSignal, Module, Document.
Edge types: DEFINED_IN (Service→Repository), DEPLOYED_TO (Service→Environment), DEPENDS_ON (Service→Service), USES (Service→DataStore), HOSTED_ON (Environment→Cluster), OWNED_BY (Service/Repository→Team/Person), MEMBER_OF (Person→Team). For completed work, an Activity carries PERFORMED (→ the actor), TOUCHED (→ the modules/features it changed), and IN_PERIOD (→ the period bucket). Use RELATED_TO only when nothing canonical fits.
Discipline
- Justify every mutation from the event payload or a tool-observed fact. If
unsure, add a
warningand keep the plan minimal — don't invent. - Supersession → invalidation. When this event makes a prior fact untrue,
add an
invalidationreferencing the prior entity/edge rather than silently overwriting. - Evidence, not vibes. Link the PR/issue/commit/url that grounds the plan
under
evidence; setconfidencelower when the inference is indirect.
Worked example — a merged PR that fixes a bug
{
"summary": "PR #482 fixes the retry-storm in the billing worker",
"episodes": [{
"name": "PR #482 body",
"episode_body": "<the PR description text>",
"source_description": "github pull_request merged"
}],
"entity_upserts": [
{"entity_key": "timeline:activity:merged:9f3a1c",
"labels": ["Activity"],
"properties": {"verb": "merged", "verb_class": "code", "title": "Merge PR #482"}},
{"entity_key": "fix:billing:retry-storm",
"labels": ["Fix"], "properties": {"title": "Bound billing retry backoff"}}
],
"edge_upserts": [
{"edge_type": "PERFORMED", "from_entity_key": "person:github:alice",
"to_entity_key": "timeline:activity:merged:9f3a1c"},
{"edge_type": "TOUCHED", "from_entity_key": "timeline:activity:merged:9f3a1c",
"to_entity_key": "module:o/r:billing.worker"},
{"edge_type": "RESOLVED", "from_entity_key": "fix:billing:retry-storm",
"to_entity_key": "bug:billing:retry-storm"}
],
"evidence": [{"kind": "pull_request", "ref": "github:pr:o/r:482"}],
"confidence": 0.9,
"warnings": []
}
Trivial PRs (typo/lint) need only the Activity — no Fix or Decision. Don't invent design decisions the PR body doesn't state.