prose-dev Atlas OpenProse docs bound to the contracts they describe
144 documents
skills/open-prose/compiler/ir-v0.md

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 ### Maintains fields (the shared truth sitting outside any #### part — e.g. a node-wide name / 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 ### Maintains with 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 a failed receipt. The reference harness implements the deterministic engine as evaluatePredicate.
  • 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": []
}