Introducing the Quine Recipe Analyzer

July 10, 2026 · by Ryan Wright · 11 min read

The recipe runs. Is it building the graph you meant?

A Quine recipe is a small amount of Cypher with a large amount of consequence. A few ingest queries and standing queries turn a firehose of events into a connected graph that the rest of your pipeline depends on. When the recipe is short, you can read it and know exactly what it does. When it grows to a dozen ingests, several standing queries, and node identities assembled from three different source systems, that certainty gets harder to hold.

The Quine Recipe Analyzer with a CIEM demo recipe loaded, showing the inferred entity graph on the left and the analysis sidebar on the right.

The hard part is that a recipe that is subtly wrong still runs. Data flows, nodes appear, and nothing throws an error. The graph underneath is just not quite the graph you intended, and you often cannot see the difference until something downstream returns a wrong answer.

The culprits are usually quiet:

At enterprise volume, the distance between "it runs" and "it does exactly what I intended" gets expensive fast. By the time a duplicated identity or a missing edge surfaces, you may have reprocessed terabytes or made decisions on a graph that was wrong in a way nobody was watching for.

That is the gap the Quine Recipe Analyzer closes. It reads a recipe and, without running it, shows you the graph it will build and the problems worth fixing first. The screenshots here run it against a sample CIEM recipe: five ingest streams and two standing queries that assemble a cloud entitlement graph. You can download that recipe at the end and follow along.

See the graph before you run it

Paste a recipe, drop a file anywhere on the page, or open one with the file picker. The analyzer parses the Cypher in every ingest and standing query, infers the node types and relationships they create, and draws them.

The inferred entity graph, with solid and dashed relationships, self-loops, and warning markers on individual node types.

Each circle is a node type, inferred from your idFrom() prefixes and falling back to labels or to the structure of the relationships around it. Each arrow is a relationship type. A solid arrow is a relationship some query creates. A dashed arrow is one your queries only ever match, which is often the first clue that an edge you expect is never actually written. Edge thickness grows with how many query steps touch a relationship, self-relationships fan out as labeled loops so several stay readable, and a variable-length traversal is marked so you can tell a single hop from a path.

The nodes carry their own signal. Color encodes supernode risk. A dashed ring means a node is only ever matched and never written. A warning triangle marks a node with findings attached. An x marks a node that some query deletes, whether from an ingest or a standing query output. Size tracks how many relationships touch the node. You can drag nodes to untangle a layout, zoom with the wheel or the buttons, double-click to refit, hide or show a structured legend, and switch between light and dark themes. The choices you make persist across loads.

The sidebar has three tabs. Details inspects what you select, Controls changes what you see, and Findings lists what to fix.

The Details tab: inspect any node or edge

On the overview, Details lists every node type with its cardinality and supernode risk, the cross-stream join keys, and a summary of the standing queries. Click a node and it becomes an inspector.

The Details tab showing the inspector for the version node: its labels, cardinality, six idFrom variants across different streams, and the exact SET expression behind each property with source line numbers.

For a node it shows the recipe appearance rule it matches, a cardinality selector, and every idFrom() variant that keys it across your queries, including the pattern-column origin of values flowing in from a standing query. It lists every property the node sets, with the exact SET expression and the query and line it came from. It shows what deletes the node, why the analyzer scored its supernode risk the way it did, and the full table of edges in and out. Click an edge instead and you get its provenance: every place that relationship is touched, with the source_type filters, UNWIND fan-out, FOREACH conditionality, and DELETE cleanup around it, and for a relationship that is only ever matched, where in the recipe that type is actually created. Any warning or error tied to what you selected sits at the top, and clicking it jumps to the finding.

The Controls tab: break the recipe down

Controls is where you change what the graph shows. Toggle the standing-query layer and the recipe node appearances on or off. Switch matched-only edges and edge labels. Below those, every ingest stream and every standing query unit gets its own chip.

The Controls tab with layer and display toggles, a chip for each ingest stream (package-registry, cloud-asset-inventory, workload-inventory, cloud-workload-telemetry, ci-pipeline-events), and a chip for each standing query.

Those chips are the fastest way to understand a dense recipe. Turn a stream off and the graph redraws with just what the rest of the recipe builds, so you can see exactly what that one stream contributes: the node types it creates, the edges only it writes, and where it overlaps with the others. Work through them one at a time and a recipe you inherited stops being a wall of Cypher and becomes a set of parts you can reason about. The tab also tracks any cardinality overrides you have set, with a count and a reset.

The Findings tab: everything worth fixing, in one place

Findings is the checklist. It counts errors, warnings, and info items in the header and lists each one. Select a finding and it explains the problem and highlights the exact line of your recipe that triggered it.

The Findings tab listing warnings and info items for the CIEM recipe, with one finding expanded to show its explanation and the offending lines of Cypher.

Errors are the things Quine rejects or that break outright: a MERGE that forces a full scan, properties on an edge, shortestPath in a MATCH, id() on a relationship, unsupported functions, and property access on a $that that is not JSON. Standing query patterns that violate the mode rules land here too.

Warnings are the quiet behavior changes: a MATCH with no id(n) = idFrom(...) anchor that scans the whole graph, label or property filters on an id-anchored node that can drop records under parallel ingest, labels you match but never set, relationships matched but never created, idFrom() keys whose toLower() or arity disagree across queries and silently duplicate nodes, a non-idempotent SET x = x + 1, nodes you match but never use, and supernode risk.

Info items are the smaller notes: duplicate CREATEs, a node keyed from more than one source, a soft delete versus purgeNode, unknown procedures, and pattern relationships that could not be mapped to a node type.

Standing queries get their own layer

Standing queries are where recipes get subtle, so the analyzer treats them as a separate, toggleable layer on top of the ingest analysis. It follows the result contract end to end. A pattern that does RETURN id(e) AS entity becomes data.entity, so a $that.data.entity anchor in an output query resolves back to the e in the pattern, and values flowing into an output idFrom() are annotated with where in the pattern they came from.

Because standing query patterns rarely use labels or idFrom, the analyzer infers their node types by constraint propagation, matching the pattern's relationship types against the edges your ingests and earlier standing queries create. Chained standing queries resolve in order. Patterns are validated against the DistinctId and MultipleValues rules, in both the v1 and v2 recipe formats, and each output is checked against the columns its pattern actually returns. Non-Cypher destinations like Kafka, HTTP, and Slack are listed and flagged as out of scope for analysis rather than guessed at. When you toggle the layer, supernode risk recomputes for whichever layers are visible.

Supernodes, with a cardinality you can correct

Every node type gets a cardinality estimate, from singleton up to per-event, read from its key fields and idFrom prefix. The analyzer flags the edges most likely to grow a node without bound: a large cardinality gap between the two ends, or a far side keyed per event, like a GUID or a timestamp, that accumulates edges forever.

The node types and supernode risk overview, with a cardinality and a risk level for each type and the role node flagged as a medium supernode risk.

That estimate is name-based, so it is advisory. When you know the data better than a field name does, override the cardinality on any node and the scoring recomputes. Overrides are saved in your browser by node name and reapplied next time, with a running count and a reset in the Controls tab.

Cross-stream join keys

The place two streams are supposed to meet is the place they quietly fail to. The overview lists every node type whose identity is keyed from more than one stream or source, with each contributing idFrom() expression side by side, so you can confirm the value spaces match before a case mismatch or a formatting difference splits one entity into two.

Cross-stream join keys, listing each idFrom expression that feeds a shared node identity across different streams.

It renders your node appearances

If your recipe defines nodeAppearances, the analyzer renders them the way the Quine UI does, evaluating the rules in order and matching on labels and property keys. Icons render with the real Ionicons webfont, literal emoji render as themselves, and each icon is drawn large enough to read at a glance, so the graph looks like the one you will see in Quine.

It takes the inputs you already have

You do not have to reshape anything to use it. It accepts a full Quine recipe in YAML or JSON, in both the v1 and v2 formats, including ingestStreams, standingQueries, and nodeAppearances. It also reads a Kubernetes ConfigMap whose data holds ingest maps, a plain map of name to ingest definition, a single Quine API ingest body, or raw Cypher with queries separated by --- lines. The header shows what you are analyzing and lets you reopen the original text, and when something will not parse it points at the offending line.

Export the schema it inferred

Once the analyzer knows the shape of your graph, it can hand that shape to the rest of your toolchain. Export a Markdown report to have AI Agents help with the full model, the join keys, and all the findings. Or export the inferred schema itself as GraphQL SDL, OWL or RDFS in Turtle, SHACL shapes, JSON-LD, a Mermaid ER diagram, GraphML, or plain JSON. The RDF formats use a base namespace you can set and that persists per browser. Cardinality is written as an annotation, never a hard constraint, and datatypes are inferred from names and default to string, so the export describes your graph without overstating what static analysis can know. Relationships used from several node types become union domains, so a shared edge is described once across all the types that create it.

Try it on the CIEM recipe, or bring your own

Give it a try. It is free and it runs right in your browser instantly.

The recipe in these screenshots is a synthetic CIEM example: five ingest streams and two standing queries that resolve a cloud entitlement graph and block a compromised deployment before it ships. Download it, open it in the analyzer, and click through everything above for yourself.

Then drop in one of your own recipes and see what it tells you about the graph you are about to create.