A dated, hashed rendition of a node: what it looked like, when, and whether it still does.
A capture is a dated, hashed rendition of something the graph already holds. A screenshot of a surfaceSurfaceExperience DesignA place inside a screen, the features that occupy it, and the rule that decides who wins itView reference →, a PDF of a report, an export of a canvas: the subject is a node and stays one, and the capture says here is what that looked like, at this moment, and here is how to tell whether it still does. It has no lifecycle, because a capture is a fact rather than a piece of work, and no structural parent, because it anchors to what it renders.
Content addressing is the older half of the idea. Identifying bytes by a hash of the bytes, rather than by a name or a location, is what makes content-addressable storage work and what makes Git's object model work: a file's identity is its hash, and a byte-identical file is the same object no matter what it is called.
The newer half comes from visual regression testing, where a stored screenshot is compared against a fresh one on every build and a difference is the signal that something moved. Both halves assume the rendition lives in a directory. UPG's move is to make it a node, so its subject is a graph reference rather than a filename convention, and so the question of what has drifted since capture is a traversal rather than a scan of a folder.
The naming is deliberate and worth stating, because it looks arbitrary until you count. artifact was already spent twice: once for the output of a workflow runWorkflow RunWorkflows & AgentsAn execution of a workflowView reference →, and once, in shipped tools, for a store of generated documents holding inline content. A third meaning of one word is how a vocabulary stops being one, so the type is named for what it does.
A team renders every surface in their design systemDesign SystemDesign SystemThe root design system entityView reference → to an image each night and drops the files in a folder. Within a month the folder is unusable. Nobody can tell which image belongs to which surface, whether the November file is newer than the October one, or which of the two hundred images changed in last night's run. The usual fix is a naming convention, and the usual outcomeOutcomeStrategyA desired business or user outcomeView reference → is a naming convention that half the pipeline respects.
Modelled as captures, each image becomes a node pointing at its surface through Capturerendersany entitycross-domain, carrying the moment it was taken and a hash of its bytes. The hash is the part that earns its place. A modification time lies on a byte-identical re-run, because the file was rewritten even though nothing changed, and a file size misses a repaint that happens to produce the same number of bytes. Only a content hash means this rendition is different from the last one. The gallery that reads these captures can now admit a changed image and leave every arrangement somebody made alone.capture_renders_node
In the Unified Product Graph, CaptureWorkspaceA dated, hashed rendition of something already in the graph: a screenshot of a surface, a PDF of a report, an export of a canvas. The subject stays a graph node; the capture records what it looked like at one moment, and the content hash says whether it still does. sits in the Workspace domain alongside captureWorkspaceWorkspaceA spatial thinking space for arranging entitiesView reference →, workspaceCompositionWorkspaceA published arrangement of graph entities that has an identity of its own: a gallery, a board, a deck. Membership can be derived by running its query, position is authored by a person, and both are stored because both are real.View reference → and compositionFramework ExerciseWorkspaceA recorded run of a product framework (RICE, OST, and the like) over specific entities, holding its inputs and outputsView reference →. It reaches its subject through one polymorphic edge, framework_exerciseCapturerendersany entitycross-domain, which can point at any node type, because anything visible can be rendered. It is lifecycle-free and containment-free: the same posture as capture_renders_nodeCompositionWorkspaceA published arrangement of graph entities that has an identity of its own: a gallery, a board, a deck. Membership can be derived by running its query, position is authored by a person, and both are stored because both are real.View reference →, and for the same reason, which is that it anchors relationally to what it shows rather than hanging under a parent. Where the bytes live is a tool decisionDecisionStrategyA recorded decision with context, rationale, and consequencesView reference →, not a spec one. compositioncapture_uri is a URI on the same footing as external_ref on the base node, so a hosted file, a local file and a relative path beside the graph are all legal and none of them is blessed.
Deletion needs no second vocabulary. A capture that was deleted is a capture node that was deleted, and a consumer renders what the graph says exists rather than what a tool's own bag remembers. That rule matters most for the tools that generate captures in bulk, because a generator that keeps its own exclusion list will eventually disagree with the graph, and the graph is the one other tools can read.
Type-specific fields on BaseNode
capture_uristringWhere the bytes are. `https://` for hosted, `file://` or a relative path for local. Required: a capture with no location renders nothing.
content_hashstringContent hash of the bytes. The regeneration signal; see the type's `@remarks` for why mtime and size are not.
hash_algorithmenumHash algorithm. Absent means `sha256`.
captured_atstringISO timestamp the capture was taken.
media_typestringIANA media type of the bytes. @example "image/png"
fidelityenumHow faithfully this rendition represents its subject. `approximate` covers a capture taken in a stand-in state or at the wrong viewport.
capture_statusenumWhether the capture succeeded.
A `blocked` capture is a real record rather than a missing one: it says the subject exists and could not be rendered, which is what stops the next run rediscovering the same obstacle. `skipped` is the deliberate exclusion.
idstringrequiredUnique identifier (UUID)
typeNodeTyperequiredDiscriminator for the entity type
titlestringrequiredDisplay name
descriptionstringOptional detailed description
statusstringLifecycle status
tagsstring[]Freeform tags for filtering