The canonical contract for the compile-phase IR consumed by the dumb reconciler, defining kind openprose.compile-phase-ir version 2 with sources, topology nodes edges entry_points and acyclic, per-node canonicalizers with facet lists, per-node postcondition validators with deterministic or render-attested mode, contract_fingerprints map, and diagnostics, plus fingerprint semantics, mount versus source identity, the named-parts facet lowering rule, the structured-backing rule, artifact locator conventions, and a compact valid example. Emitting or validating dist/manifest.next.json or checking IR topology and fingerprint rules.
role: compile-phase-ir-contract
summary: |
Canonical compile-phase IR contract for prose compile. Load this before
emitting or validating dist/manifest.next.json. The IR carries the
compile-phase OUTPUTS — the topology world-model, per-node canonicalizers,
per-node postcondition validators, and frozen contract fingerprints — NOT a
judge-era activations/criteria manifest.
see-also:
- index.prose.md: ProseScript compiler program
- ../responsibility-runtime.md: Compile/run reconciler doctrine
- ../forme.md: Forme wiring semantics (the topology world-model)
- ../concepts/reconciler.md: The fingerprint-comparison reconciler
Compile-Phase IR
The compile-phase IR is generated JSON for the dumb reconciler. It is not an authoring surface. Keep authored intent in Markdown; keep compiled intent in JSON.
This is the compile-phase seam: the compile phase emits it on contract-set
change, the run phase (the reconciler) consumes it, and this doc authors to it.
There is no judge, no verdict, no pressure, and no fulfillment activation in the
IR. Commit-gating is compiled postcondition validators plus render
self-attestation, never an LLM judging "did this change" at wake time
(see concepts/reconciler.md).
This document is the IR contract: a CompilePhaseIR object wrapped in a thin
doc envelope of sources and diagnostics. The CompilePhaseIR type the
reference harness exports from its SDK tracks this document; a conforming
harness must match what is written here.
Emit only the fields listed here. Unknown notes, provider details, payload
shape, confidence, and source commentary belong in diagnostics, not in custom
IR fields.
Top Level
{
"kind": "openprose.compile-phase-ir",
"version": 2,
"sources": [],
"topology": {
"nodes": [],
"edges": [],
"entry_points": [],
"acyclic": true
},
"canonicalizers": [],
"postconditions": [],
"contract_fingerprints": {},
"diagnostics": []
}
All fields must be present. kind is the literal
"openprose.compile-phase-ir". version is the integer 2 (it tracks the
SKILL runtime_contract, which the format re-cleave bumped from 1 to 2).
sources, canonicalizers, postconditions, and diagnostics are arrays.
topology is a single object. contract_fingerprints is an object map.
Paths are root-relative, forward-slash paths with no empty, current, parent, or absolute segments.
Fingerprints
A fingerprint is a string token that changes if and only if the
semantically-material content changed (the fingerprint rules in
concepts/reconciler.md). The reference computation is sha256:<64 lowercase hex> — a content address over a canonical serialization. The IR carries
fingerprints as opaque strings; the reconciler only ever compares them.
Three fingerprints of meaning appear: the contract-fingerprint of each node's own contract, the input-fingerprint of each upstream facet a node subscribes to, and the world-model-fingerprint of a node's own published truth. The compile phase freezes the first; the run phase observes the other two on receipts.
A facet is a named, independently-subscribable part of a node's truth. The
reserved facet "@atomic" is the whole-truth fingerprint; a node that declares
no facets exposes the singleton { "@atomic": token } map. Facet arrays in this
IR always include "@atomic".
Sources
{ "path": "src/competitor-monitor.prose.md", "kind": "responsibility", "name": "competitor-monitor" }
sources is the discovered contract set the compile phase read. Allowed kind
values: responsibility, function, gateway, pattern, test, unknown.
name is optional.
There is no system kind and no service kind. Composition is intra-node
ProseScript call or a cross-node subscription, never an internally-autowired
graph kind (the authored kinds in contract-markdown.md). A kind: function is a
called helper with no world-model and no node identity; functions appear in
sources only when discovered, and never appear as topology nodes.
Topology
The topology world-model is Forme's output: the resolved DAG drawn from the
contract set (forme.md). It is a maintained truth like any
other. The reconciler reads edges to resolve propagation targets.
{
"nodes": [
{
"node": "competitor-monitor",
"contract_fingerprint": "sha256:0000000000000000000000000000000000000000000000000000000000000001",
"wake_source": "input"
}
],
"edges": [
{
"subscriber": "risk-brief",
"producer": "competitor-monitor",
"facet": "funding"
}
],
"entry_points": ["stargazer-events"],
"acyclic": true
}
nodes
Each node is one mounted producer (a responsibility or gateway). Required
fields: node (the mount identity; see "Node identity" below),
contract_fingerprint (the frozen fingerprint of its contract/source), and
wake_source.
wake_source is one of input, self, or external: input-driven by default,
self-driven when ### Continuity declares a cadence, external-driven for a
gateway. It is the node's intrinsic wake-source declaration, carried from
### Continuity.
Functions are never nodes. Patterns expand into nodes at compile time; the expanded responsibilities appear here, the pattern source does not.
Node identity
node is mount identity: an opaque string assigned when a contract is
mounted, unique within a manifest, and stable across recompiles of an unchanged
contract set. A single mount of a contract — the case every example in this
document and every compile of src/ alone produces — defaults it to the
contract's slug (frontmatter name:), which is why the worked examples key
nodes as "competitor-monitor" and "stargazer-events". When a harness mounts
one contract more than once, or instantiates a facet family once per entity
(contract-markdown.md, "Facet families and per-entity mounts"), the per-mount
node keys are the harness's to assign; the compiler emits one node per contract
and never an enumeration.
contract_fingerprint is what ties a node to its source: two nodes mounted
from the same contract carry the same fingerprint under different node keys.
Frontmatter id:, when an author declares it, is the source identity behind
the node — it survives filename and name: renames where the slug does not —
but it is not emitted anywhere in a version 2 manifest; the node record has no
id field. edges, entry_points, canonicalizers, postconditions, and
contract_fingerprints all refer to a node by its node key and nothing else.
edges
Each edge is one resolved subscription:
subscriber.Requires.<facet-contract> → producer.Maintains.<facet>. Required
fields: subscriber (the consuming node), producer (the producing node),
facet (the producer facet consumed; "@atomic" when the producer declares no
facets). subscriber and producer must be node ids present in nodes.
Fan-in (one need, many producers) is several edges with the same subscriber
and facet-contract but different producers; each adds a slot to the
subscriber's input tuple (the diamond rule in forme.md). Edges are not a step
list and carry no ordering; propagation order falls out of the DAG.
entry_points
entry_points lists the node ids that are external-driven ingress points
(gateways) — the nodes a webhook / cron / manual trigger turns into an edge
receipt at the system's edge. Every entry point must be a
node with wake_source: "external".
acyclic
acyclic is Forme's own acyclicity postcondition over edges. It is computed
by the deterministic cycle check (the reference harness implements it as
detectReceiptCycles, the kept-half kernel DFS). The acyclicity check rejects
graph cycles only; legitimate feedback (a node's output shaping its next
input) is self-driven ### Continuity, not a back-edge — loops live in time,
not in edges. When a contract set is irreducibly cyclic, acyclic is false
and a severity: error diagnostic names the cycle; the compiler does not write
the IR.
Canonicalizers
One canonicalizer per node. The canonicalizer is the compiled, deterministic
lowering of the node's ### Maintains canonicalization spec; it travels with
the compiled contract and a standalone render applies it locally to fingerprint
its own receipt. canonicalizer(world-model) → fingerprints.
The ####-part → facet lowering (the named-parts rule)
The compile phase reads the named parts of ### Maintains into the facet
boundaries this canonicalizer emits. A #### sub-heading inside ### Maintains
is a facet: its heading text is the facet name and its body's material field
paths are that facet's paths (the named-parts rule in contract-markdown.md).
The lowering is:
- Each
#### <name>part → one facet<name>whose fingerprint is computed over that part's material field paths. Materiality and normalization (text/sets/ numbers) stay prose inside the part, lowered at compile time to that facet's material paths; a part is default-material within itself (everything the part names is material unless the part's prose drops it). - Un-facetted top-level
### Maintainsfields (the shared truth sitting outside any####part — e.g. a node-widename/last_corroborated) bind to the atomic facet only. They move only the always-on"@atomic"token, never a declared facet's token. - Name no parts → atomic-only. A
### Maintainswith no####parts lowers to a single facet["@atomic"]over the whole material truth — the free default and the leaf-node case. This is byte-identical to the pre-facet behaviour; faceting is purely additive.
This is the JSON realization of the CanonicalizationSpec.facets: FacetSpec[]
input the harness's canonicalizer-compiler consumes: one FacetSpec { facet: <heading>, paths: <material fields> } per #### part, plus the
reserved atomic facet the compiler always prepends. The facets array below is
the output projection of that lowering — the facet names the canonicalizer
emits, atomic always included.
{
"node": "competitor-monitor",
"artifact": "dist/canonicalizers/competitor-monitor.js",
"facets": ["@atomic", "funding", "hiring", "product-launches"]
}
Here competitor-monitor's ### Maintains declared three #### parts —
#### funding, #### hiring, #### product-launches — so the canonicalizer
emits three declared facets plus the always-on atomic token over the whole truth
(the worked competitor-activity-monitor example in contract-markdown.md).
Required fields: node (a node id present in topology.nodes), artifact
(a root-relative locator for the compiled canonicalizer artifact), and facets
(the facet boundaries the canonicalizer emits). facets always includes
"@atomic"; a leaf truth that declares no facets has facets: ["@atomic"].
The facets listed here are the producer side of the edges: every
edge.facet whose producer is this node must appear in this node's facets.
The structured-backing rule (stated for authors in contract-markdown.md):
anything subscribed must have a structured, canonicalizable backing. Free-form
rendered prose is a derived projection excluded from the fingerprint. The
compiler lints subscribed fields without structured backing and surfaces them as
a diagnostic.
Artifact locators
artifact is a locator, not a payload: the compiled canonicalizer lives on
disk and the manifest says where. Like every path in this IR it is root-relative
with forward slashes, and the root it is relative to is the OpenProse root —
the directory that holds src/ and dist/, the parent of the compile output
directory — so dist/canonicalizers/competitor-monitor.js names
<openprose-root>/dist/canonicalizers/competitor-monitor.js. The compiler
writes the artifact and the manifest in the same pass; the run phase resolves
the locator against that same root when it loads dist/manifest.active.json
(promoted from manifest.next.json), so a manifest and its artifacts move
together or not at all. The same rule applies to the artifact field of every
postconditions entry below.
Postconditions
One postcondition validator per node. The folded-in ### Criteria compile to
validators. There is no separate judge beat.
{
"node": "competitor-monitor",
"artifact": "dist/postconditions/competitor-monitor.js",
"mode": "deterministic"
}
Required fields: node (a node id present in topology.nodes), artifact
(a root-relative locator for the compiled validator artifact), and mode.
mode is one of:
deterministic— the harness verifies the validator on commit; a render that fails verification commits nothing and writes afailedreceipt. The reference harness implements the deterministic engine asevaluatePredicate.render-attested— the postcondition is irreducibly semantic; the render self-polices it before signing.
Either way there is no LLM in the wake/commit decision.
Contract Fingerprints
contract_fingerprints is a { node → fingerprint } map: the per-node contract
fingerprints frozen at compile time. Every node id in topology.nodes must
have an entry, and each entry must equal that node's contract_fingerprint.
Editing a node's ### Maintains (or any material part of its contract) moves
its contract fingerprint, which causes a memo miss and a forced render at run
time (a schema migration is a forced render).
These are the first half of the memo key (contract_fingerprint, input_fingerprints) — and nothing else is in the key (the memo key in
concepts/reconciler.md).
Diagnostics
{
"severity": "warning",
"message": "Subscribed field `summary` has no structured backing; it is excluded from the fingerprint.",
"sourcePath": "src/competitor-monitor.prose.md"
}
Allowed severities: info, warning, error. sourcePath is optional and
must reference a discovered source when present.
The compiler program must not write manifest.next.json when any diagnostic
has severity error (e.g. an ambiguous Forme match, an unsatisfied
subscription, or a cyclic contract set). Warnings and info diagnostics may be
written with a valid IR.
A wiring failure is always a surfaced diagnostic, never a silent guess: no
producer for a ### Requires facet, or an ambiguous match between candidate
producers, is reported (the diagnostics step in forme.md).
Compact Valid Example
{
"kind": "openprose.compile-phase-ir",
"version": 2,
"sources": [
{
"path": "src/competitor-monitor.prose.md",
"kind": "responsibility",
"name": "competitor-monitor"
}
],
"topology": {
"nodes": [
{
"node": "competitor-monitor",
"contract_fingerprint": "sha256:0000000000000000000000000000000000000000000000000000000000000001",
"wake_source": "self"
}
],
"edges": [],
"entry_points": [],
"acyclic": true
},
"canonicalizers": [
{
"node": "competitor-monitor",
"artifact": "dist/canonicalizers/competitor-monitor.js",
"facets": ["@atomic"]
}
],
"postconditions": [
{
"node": "competitor-monitor",
"artifact": "dist/postconditions/competitor-monitor.js",
"mode": "render-attested"
}
],
"contract_fingerprints": {
"competitor-monitor": "sha256:0000000000000000000000000000000000000000000000000000000000000001"
},
"diagnostics": []
}