Relations and Graph Rules
A relation declares one structural link between components and resources: a call, a callback, a provided resource, a multi-party coordination, or a link of a custom kind. Relations are top-level declarations, never members of a component. They are also the only structural links in the Shape model: owns, grants, and effect entries add no edges, and the checker infers none from source. Each relation is a hyperedge over two or more endpoints, and graph rules query the resulting hypergraph.
Relations
Section titled “Relations”This model passes shp check:
module editor
resource Revision
component Editor {}
component RevisionLog {}
relation EditorCallsLog { kind calls connects Editor -> RevisionLog}
relation LogCallsEditor { kind callbacks connects RevisionLog -> Editor}
relation RevisionLineage { kind generated_from connects Revision -> RevisionLog}
relation RevisionWritePath { kind coordinated_call connects Editor -> RevisionLog -> Revision roles { Editor as caller, RevisionLog as writer } summary "Editor writes revisions only through RevisionLog."}| Member | Presence | Meaning |
|---|---|---|
| name | Required, unique (duplicate relation otherwise) | Identifies the relation in diagnostics and shp graph output. A stable name keeps both reviewable. |
kind | Required, once | A prelude kind (calls, callbacks, provides, coordinated_call) or any other identifier. See Kinds and traversal. |
connects | Required, once | Two or more distinct endpoints, ordered as A -> B -> C or unordered as { A, B, C }. |
roles | Optional, once | Labels such as { Editor as caller }. Each must name a connects endpoint, at most once. shp graph prints them; no rule reads them. |
summary | Optional, once | Review text, printed by shp graph after //. |
expects | Optional, repeatable | expects Endpoint fingerprint provider("value") pins a resource endpoint’s fingerprint. The endpoint must be a connects endpoint and a resource, otherwise invalid relation. A missing or different fingerprint gives stale fingerprint expectation, and a removed endpoint resource gives unknown relation_endpoint. See Generate Drafts from Source. |
Endpoint resolution
Section titled “Endpoint resolution”Each endpoint names a component or a resource. A bare name resolves in its own module first, then through the module’s imports, and module::Name names another module’s declaration directly. Resolution fails in three ways:
| Condition | Diagnostic |
|---|---|
| No component or resource has the name. | unknown relation_endpoint |
| A component and a resource share the name. | invalid relation, with the reason endpoint Name resolves to both a component and a resource |
| More than one imported module declares the name. | ambiguous relation_endpoint; qualify the reference |
Kinds and traversal
Section titled “Kinds and traversal”forbid path and forbid hypercycle walk traversal steps, and each kind contributes steps differently:
| Kind | Endpoints | Traversal steps | Typical use |
|---|---|---|---|
calls | Exactly two, ordered A -> B | One step, A → B | A component calls another |
callbacks | Exactly two, ordered A -> B | One step, A → B | A callback edge, often paired with calls |
provides | Exactly two, ordered: a component, then a resource | One step, A → B | A component provides a resource or interface |
coordinated_call | Two or more, ordered | One step per consecutive pair | Multi-party coordination, such as a write path or saga |
| Any other identifier | Two or more, ordered or unordered | None | Lineage or documentation links, such as generated_from |
Only provides constrains endpoint kinds; the other kinds accept components and resources in any position. The typical-use column is guidance, not a check. In the model above, RevisionWritePath contributes the steps Editor → RevisionLog and RevisionLog → Revision, while RevisionLineage contributes none. Custom kinds appear in shp graph output but never in path or cycle search.
Validity
Section titled “Validity”A relation that breaks a structural rule gives invalid relation, printed as relation module::Name is invalid: reason. The rules are:
kindandconnectsare present, and no member other thanexpectsrepeats.- No endpoint repeats in
connects. - The endpoint count and ordering suit the kind, as listed in Kinds and traversal.
- A
providesrelation connects a component to a resource. Otherwise the reason isprovides provider X must be a componentorprovides target X must be a resource. - Each role names a
connectsendpoint, and no endpoint has two roles.
A relation with a missing kind or connects, a repeated endpoint, or the wrong count or ordering for its kind is dropped from the hypergraph, so shp graph and graph rules do not see it until it is fixed.
Graph rules
Section titled “Graph rules”A rule is a named top-level declaration whose body holds forbid clauses. Each graph-rule diagnostic names the rule. A rule can also derive a final forbid from a trait condition; see Effect Model.
forbid provides
Section titled “forbid provides”forbid provides R except C rejects every provides relation whose target is the resource R and whose provider is not the component C. except is optional and names exactly one component, so no rule form allows two providers; without except, every provider of R is rejected. R must be a declared resource and C a declared component (unknown resource and unknown component otherwise).
module publishing
resource StoryEndpoint
component Editor {}
component Publisher {}
relation EditorProvidesStories { kind provides connects Editor -> StoryEndpoint}
relation PublisherProvidesStories { kind provides connects Publisher -> StoryEndpoint}
rule publisher_only_story_endpoint { forbid provides StoryEndpoint except Publisher}shp check exits 1:
error: forbidden provides
Editor provides StoryEndpoint via relation EditorProvidesStories.rule publisher_only_story_endpoint forbids provides StoryEndpoint except Publisher.
caused by: - shape/publishing.shape: relation EditorProvidesStories - shape/publishing.shape: rule publisher_only_story_endpoint forbids provides StoryEndpointPublisherProvidesStories is allowed, and removing or redirecting EditorProvidesStories makes the model pass. The rule emits one diagnostic per offending relation. Written without except, as forbid provides StoryEndpoint in a rule named no_story_endpoint, it reports both relations, and each diagnostic’s second line reads rule no_story_endpoint forbids provides StoryEndpoint.
forbid path
Section titled “forbid path”forbid path A -> B over K1 or K2 rejects a directed path from A to B in which every step comes from a relation of a listed kind. The over list is required; without it the file does not parse. Direction matters, and kinds outside the list cannot complete a path.
module feed
resource PrivateDraft
component Feed {}
component Recommender {}
relation FeedCallsRecommender { kind calls connects Feed -> Recommender}
relation RecommenderProvidesDrafts { kind provides connects Recommender -> PrivateDraft}
rule no_feed_to_drafts { forbid path Feed -> PrivateDraft over calls or provides}shp check exits 1:
error: forbidden path
rule no_feed_to_drafts rejects this dependency path: calls FeedCallsRecommender: Feed -> Recommender provides RecommenderProvidesDrafts: Recommender -> PrivateDraftwitness: Feed -> Recommender -> PrivateDraft
caused by: - shape/feed.shape: rule no_feed_to_drafts forbids path Feed -> PrivateDraft over calls or provides - shape/feed.shape: relation FeedCallsRecommender - shape/feed.shape: relation RecommenderProvidesDraftsA forbid path rule must meet these requirements, and a rule that fails one reports the listed diagnostic instead of searching:
AandBare distinct.forbid path Feed -> Feedis aninvalid rulewith the reasonforbidden path endpoints must be distinct; use forbid hypercycle for cycles.- Each endpoint names a declared component or resource (
unknown relation_endpointotherwise). - Every listed kind has traversal steps. A custom kind makes the rule invalid:
forbid path Revision -> RevisionLog over generated_fromgivesinvalid rulewith the reasonrelation kind generated_from has no directed traversal semantics.
A relation with an unresolved or ambiguous endpoint, or a provides relation whose provider is not a component or whose target is not a resource, contributes no steps to a path.
forbid hypercycle
Section titled “forbid hypercycle”forbid hypercycle over K1 or K2 rejects a directed cycle built from steps of the listed kinds. Without over, it searches the steps of every kind that has traversal. Adding this rule to the first model on this page:
rule no_runtime_cycle { forbid hypercycle over calls or callbacks}makes shp check exit 1:
error: forbidden hypercycle
rule no_runtime_cycle rejects this hypercycle: calls EditorCallsLog callbacks LogCallsEditorwitness: Editor -> RevisionLog -> Editor
caused by: - shape/editor.shape: rule no_runtime_cycle forbids hypercycle over calls or callbacks - shape/editor.shape: relation EditorCallsLog - shape/editor.shape: relation LogCallsEditorThe over list is not validated. A custom or misspelled kind contributes no steps, so a rule whose over list names only such kinds never matches; it does not fall back to searching every kind. In the first model, RevisionWritePath steps from RevisionLog to Revision and RevisionLineage links Revision back to RevisionLog, yet the model still passes (Shape check passed.) with this rule added:
rule no_lineage_cycle { forbid hypercycle over coordinated_call or generated_from}Witnesses
Section titled “Witnesses”Each forbid path and each forbid hypercycle reports at most one witness, even when the model contains several violating paths or disjoint cycles:
- The witness is the shortest, with the fewest steps.
- Ties break on the vertex sequence in codepoint order, then on relation kind, then on relation name, so the witness does not depend on declaration order.
- A hypercycle witness starts at its codepoint-smallest vertex and lists each relation once, in walk order. A
coordinated_callthat contributes several steps appears once. - A path witness prints one line per step, as
kind Name: From -> To, so a relation that contributes two steps appears twice.
Fixing the reported witness can expose another, so a model can need several rounds of fixes before the rule passes. The usual fix breaks or redirects a cited relation; weakening the rule changes the claim instead. Rule Evaluation describes the search algorithms.
Inspecting the graph
Section titled “Inspecting the graph”shp graph prints the hypergraph without running checks:
shp graph stats [--kind KIND]prints counts of vertices, relations per kind, incidences, arity, and isolated vertices. It sizes up a model before a large relation edit.shp graph show SYMBOL [--kind KIND]prints the relations incident to a component or resource, or a single relation by name.shp graph all [--kind KIND]prints every relation, grouped by kind.
For the first model on this page, shp graph show Editor prints:
Editor (component) callbacks LogCallsEditor: RevisionLog (component) -> Editor (component) calls EditorCallsLog: Editor (component) -> RevisionLog (component) coordinated_call RevisionWritePath: Editor (component) as caller -> RevisionLog (component) as writer -> Revision (resource) // Editor writes revisions only through RevisionLog.Each line reads kind Name: endpoints, with the endpoint’s declaration kind in parentheses, its role after as, and the summary after //. Unordered endpoints print as { A, B, C }. Flags and exit codes are in the CLI Reference.