diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 2fc1eec8..b4e83656 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "open-prose", "description": "Write a Markdown contract (`.prose.md`). Your agent reads it, wires services, runs subagents, and leaves an auditable trace.", - "version": "0.17.0", + "version": "0.18.0", "license": "MIT", "homepage": "https://github.com/openprose/prose", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index a2604adb..020e7d0f 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "open-prose", - "version": "0.17.0", + "version": "0.18.0", "description": "Write a Markdown contract (`.prose.md`). Your agent reads it, wires services, runs subagents, and leaves an auditable trace.", "license": "MIT", "homepage": "https://github.com/openprose/prose", @@ -25,10 +25,7 @@ "shortDescription": "Write a Markdown contract (`.prose.md`). Your agent reads it, wires services, runs subagents, and leaves an auditable trace.", "longDescription": "OpenProse is a programming language for AI sessions. Write a Markdown contract: an agent reads it, wires the services, runs the subagents, passes artifacts through the filesystem, and leaves a durable trace under the active OpenProse root. Plain prompts are great for one-off work; they get messy when the same process needs roles, handoffs, retries, memory, or a receipt. The plugin activates on `prose ...`, on `.prose.md` files, and on requests for reusable multi-agent orchestration.", "category": "Productivity", - "capabilities": [ - "Read", - "Write" - ], + "capabilities": ["Read", "Write"], "logo": "./assets/plugin/logo.png", "composerIcon": "./assets/plugin/composer-icon.png", "brandColor": "#8a6b2e", diff --git a/README.md b/README.md index 40a2af64..4337d180 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ The contracts in this repo are **harness-agnostic**: OpenProse Markdown runs on ## Harnesses The contracts in this repo are harness-agnostic: any Prose-Complete agent host runs them, and the -spec ([`spec/02-Harness.md`](spec/)) says what a conforming harness must do. The reference harness, +spec ([`spec/02-Harness.md`](spec/02-Harness.md)) says what a conforming harness must do. The reference harness, **Reactor** (`@openprose/reactor`, `@openprose/reactor-cli`, `@openprose/reactor-devtools`, the `reactor` binary), now lives at **[github.com/openprose/reactor](https://github.com/openprose/reactor)** and is experimental (alpha): early software with no stability guarantees, to be evaluated on your own judgement. @@ -79,7 +79,7 @@ Installs keep working under the same names. In the spirit of the receipts: -- **The language:** the skill ships at `version 0.16.0`, `runtime_contract 2`; the spec ([`spec/`](spec/)) and the example corpus are migrated to the current vocabulary. The overhaul is recent: if you find a surface still speaking the old model, that's a bug, and we want the issue. +- **The language:** the skill's version of record is the `version:` frontmatter in [`skills/open-prose/SKILL.md`](skills/open-prose/SKILL.md) (its `runtime_contract` carries machine compatibility separately); the spec ([`spec/`](spec/)) and the example corpus are migrated to the current vocabulary. The overhaul is recent: if you find a surface still speaking the old model, that's a bug, and we want the issue. - **Benchmarks are openly pending, on purpose.** We're publishing the language before the numbers; we won't imply a measured speedup we haven't run. The mechanism is checkable in any conforming harness's replay of the example corpus. - The **fixpoint** (topology-as-responsibility) is specified and deferred; facet inference and ledger compaction are named roadmap. - **Harness status** (what is built, what the receipts do and do not yet prove) is documented by each harness; for the reference harness see [Harnesses](#harnesses). diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 37fe0d4a..15d62f33 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -58,7 +58,19 @@ write_field() { tmp="$(mktemp)" case "$kind" in json) - jq --arg v "$value" "$(json_expr "$field") = \$v" "$REPO_ROOT/$path" > "$tmp" + # Rewrite only the line that carries the field. Round-tripping the file + # through jq re-serializes the whole manifest (it once re-expanded a + # one-line array to four lines), so a bump must touch one line and leave + # the rest byte-identical. Reads and --check still go through jq. + local pattern="^([[:space:]]*\"${field}\"[[:space:]]*:[[:space:]]*\")[^\"]*(\")" + local hits + hits="$(grep -cE "$pattern" "$REPO_ROOT/$path" || true)" + if [[ "$hits" != "1" ]]; then + rm -f "$tmp" + echo "error: expected exactly one \"$field\" line in $path (found $hits)" >&2 + exit 1 + fi + sed -E "s/${pattern}/\1${value}\2/" "$REPO_ROOT/$path" > "$tmp" mv "$tmp" "$REPO_ROOT/$path" ;; shell-var) diff --git a/scripts/mint-contract-id.mjs b/scripts/mint-contract-id.mjs new file mode 100755 index 00000000..96dfc7ec --- /dev/null +++ b/scripts/mint-contract-id.mjs @@ -0,0 +1,149 @@ +#!/usr/bin/env node +// Mint Contract Markdown identities. +// +// A `kind: responsibility` or `kind: gateway` file may carry `id:` frontmatter +// to give the contract an identity that survives filename and `name:` renames; +// without one, the slug is the identity. The format is defined in +// skills/open-prose/contract-markdown.md (Frontmatter): a UUIDv7-compatible +// 16-byte value rendered as uppercase Crockford base32, 26 characters from +// `0-9A-HJKMNP-TV-Z`, minted once and never hand-typed. +// +// node scripts/mint-contract-id.mjs print one fresh id +// node scripts/mint-contract-id.mjs --ensure [--add] … repair ids in place +// node scripts/mint-contract-id.mjs --check [--add] … report, change nothing +// +// `--ensure` replaces an `id:` that does not match the format, on a file of any +// kind, and never touches a well-formed one. It does not add an `id:` where +// none exists: the field is optional, so a missing id is not a defect. Pass +// `--add` to also insert one on responsibility and gateway files that lack it, +// placed after `version:` (or after `kind:` when there is no `version:`). +// `--check` runs the same logic and reports what `--ensure` would change. +// +// No dependencies; runs on any Node with `node:crypto` and `node:fs`. +import { randomBytes } from "node:crypto"; +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; +const ID_LINE = /^id:\s*([0-9A-HJKMNP-TV-Z]{26})\s*$/m; +const ANY_ID_LINE = /^id:.*$/m; +const KIND_LINE = /^kind:\s*(\S+)\s*$/m; +const VERSION_LINE = /^version:.*$/m; +const MAY_CARRY_ID = new Set(["responsibility", "gateway"]); + +// UUIDv7: 48-bit millisecond timestamp, 4-bit version (7), 2-bit variant (10), +// and 74 random bits. Sorting the rendered id sorts by mint time. +export function uuidv7Bytes(now = Date.now()) { + const bytes = randomBytes(16); + let ms = BigInt(now); + for (let i = 5; i >= 0; i -= 1) { + bytes[i] = Number(ms & 0xffn); + ms >>= 8n; + } + bytes[6] = (bytes[6] & 0x0f) | 0x70; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + return bytes; +} + +// 128 bits packed big-endian into 26 five-bit groups (130 bits); the two +// leading pad bits are zero, so the first character is always `0`–`7`. +export function toCrockford(bytes) { + let n = 0n; + for (const b of bytes) n = (n << 8n) | BigInt(b); + let out = ""; + for (let shift = 125n; shift >= 0n; shift -= 5n) { + out += CROCKFORD[Number((n >> shift) & 31n)]; + } + return out; +} + +export function mintId() { + return toCrockford(uuidv7Bytes()); +} + +function frontmatterRange(source) { + if (!source.startsWith("---\n")) return null; + const end = source.indexOf("\n---", 4); + if (end === -1) return null; + return { start: 4, end: end + 1 }; // body of the block, exclusive of fences +} + +// Returns { status, source } where status is one of: +// "ok" a well-formed id is already present +// "reminted" a malformed id was replaced +// "minted" a missing id was inserted (only with `add`) +// "skipped" no id present and none added +// "invalid" no frontmatter block to write into +export function ensureId(source, { add = false } = {}) { + const range = frontmatterRange(source); + if (!range) return { status: "invalid", source }; + const block = source.slice(range.start, range.end); + if (ID_LINE.test(block)) return { status: "ok", source }; + + const fresh = `id: ${mintId()}`; + + if (ANY_ID_LINE.test(block)) { + const next = block.replace(ANY_ID_LINE, fresh); + return { + status: "reminted", + source: source.slice(0, range.start) + next + source.slice(range.end), + }; + } + + const kind = KIND_LINE.exec(block)?.[1] ?? ""; + if (!add || !MAY_CARRY_ID.has(kind)) { + return { status: "skipped", source }; + } + + const anchor = VERSION_LINE.exec(block) ?? KIND_LINE.exec(block); + if (!anchor) return { status: "invalid", source }; + const at = anchor.index + anchor[0].length; + const next = `${block.slice(0, at)}\n${fresh}${block.slice(at)}`; + return { + status: "minted", + source: source.slice(0, range.start) + next + source.slice(range.end), + }; +} + +function usage() { + console.error( + [ + "usage:", + " node scripts/mint-contract-id.mjs", + " node scripts/mint-contract-id.mjs --ensure [--add] ...", + " node scripts/mint-contract-id.mjs --check [--add] ...", + ].join("\n"), + ); + process.exit(2); +} + +function main(argv) { + if (argv.length === 0) { + console.log(mintId()); + return 0; + } + const mode = argv[0]; + if (mode !== "--ensure" && mode !== "--check") usage(); + const add = argv.includes("--add"); + const files = argv.slice(1).filter((a) => a !== "--add"); + if (files.length === 0) usage(); + + let failures = 0; + for (const file of files) { + const before = readFileSync(file, "utf8"); + const { status, source } = ensureId(before, { add }); + const changed = status === "minted" || status === "reminted"; + if (status === "invalid") failures += 1; + if (mode === "--ensure" && changed) writeFileSync(file, source); + if (mode === "--check" && changed) failures += 1; + if (status !== "ok" && status !== "skipped") { + console.log(`${status.padEnd(8)} ${file}`); + } + } + return failures === 0 ? 0 : 1; +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + process.exit(main(process.argv.slice(2))); +} diff --git a/skills/open-prose/SKILL.md b/skills/open-prose/SKILL.md index a4908710..cd64f659 100644 --- a/skills/open-prose/SKILL.md +++ b/skills/open-prose/SKILL.md @@ -1,6 +1,6 @@ --- name: open-prose -version: 0.17.0 +version: 0.18.0 runtime_contract: 2 description: | Activate when the user types `prose ...`, opens a `.prose.md` file with diff --git a/skills/open-prose/changelog.md b/skills/open-prose/changelog.md index 7a972094..9f8d14b5 100644 --- a/skills/open-prose/changelog.md +++ b/skills/open-prose/changelog.md @@ -15,9 +15,11 @@ plan. ## Current Conventions - Authored source files are `*.prose.md`. -- `kind: responsibility` files declare stable `id:` frontmatter. The id is - generated once by tooling as UUIDv7-compatible bytes, rendered as uppercase - Crockford base32, and preserved across display-name and filepath renames. +- `id:` frontmatter is optional on responsibilities and gateways: 26 characters + of uppercase Crockford base32 minted by `scripts/mint-contract-id.mjs`, for + identity that survives renames. The slug is the default identity. +- `version:` frontmatter is optional, author-owned provenance in semver form. + The compiler ignores it; it is not the skill version. - `### Tools` applies to `function` and `responsibility`. Tool declarations support both `cli:` and `mcp:` and fail closed when the host cannot resolve a declared capability. Resolved responsibility tools are @@ -45,6 +47,39 @@ plan. ## History +- `v0.18.0`: **Example corpus made compiler-clean.** `id:` frontmatter is now + optional on responsibilities and gateways: the slug is the identity by + default, and a declared id is the source identity that survives filename and + `name:` renames. A missing `id:` is no longer a compile error. Where an id is + present it must be 26 characters of uppercase Crockford base32, minted by + `scripts/mint-contract-id.mjs` (which also repairs a malformed id in place) + and never hand-typed; the hand-typed slug ids in the examples were dropped, + not replaced, since they named nothing the slug does not. `version:` + frontmatter is documented as optional author-owned provenance in semver form, + ignored by the compiler. `contract-markdown.md` gains *Facet families and + per-entity mounts*: a `#### name:` heading under `### Maintains` + declares a family, a placeholder facet-need in `### Requires` subscribes to + one member, `# Title [instance]` marks a contract mounted once per entity, and + the harness binds members at mount time while the compiler emits the family. + Every `###` heading in the examples is now a canonical section + (`### Continuity: external-driven` became `### Continuity` with an + `- external-driven` bullet; `### Postconditions` and `### Facets` folded into + `### Maintains`; `### Watches` moved into `### Receives`), and every + `### Requires` names a producer in its own example (competitor-activity gains + a `signal-feeds` gateway, research-inbox-triage a `research-registry` + gateway). Example READMEs whose node and edge counts exceed what mounting + `src/` alone produces now attribute the count to the reference harness's + per-entity expansion. `compiler/ir-v0.md` specifies node identity (`node` is + mount identity, defaulting to the slug for a single mount) and `artifact` + locators (paths relative to the OpenProse root); `concepts/reconciler.md` + gives the receipt `cost` field its sub-shape + (`{ provider, model, tokens: { fresh, reused }, surprise_cause }`). + Implementation-status claims in `spec/01-Language.md` and + `spec/03-AuthoringPattern.md` name the reference harness version they + describe. `runtime_contract` is unchanged (2): no source rewrite is needed. + `prose upgrade` may drop an `id:` that merely repeats the slug; it must never + add one. + - `v0.17.0`: introduced `prose init` / `prose compose` and the obligation-centered `std/ops/compose` directory package, including bounded architectural fronts, progressive Contract source, derived visual views, diff --git a/skills/open-prose/compiler/index.prose.md b/skills/open-prose/compiler/index.prose.md index 66930788..25435cf7 100644 --- a/skills/open-prose/compiler/index.prose.md +++ b/skills/open-prose/compiler/index.prose.md @@ -13,7 +13,7 @@ This is a pinned ProseScript compiler program. It is not a mounted node and is not Forme-wired: the compiler itself owns its execution order and uses short, isolated sessions to keep each lowering step on a narrow context budget. It is the **intelligent compile phase**; the run phase that reads its output is dumb -(`architecture.md` §2). +(the two phases in `concepts/reconciler.md`). ### Parameters @@ -98,9 +98,10 @@ agent responsibility_compiler: Preserve Goal, Requires, Maintains, and Continuity as the node's contract. Derive wake_source from Continuity: input-driven by default, self when a cadence is declared, external for a gateway. - Use frontmatter `id:` as the responsibility identity backing the node. Never - derive identity from `name:`, filepath, title, or a slug; those are display - and source-location fields only. + The node key in the topology is mount identity, assigned at mount; a single + mount of a contract defaults it to the slug. Frontmatter `id:`, when declared, + is the source identity behind that node and survives renames. Never treat + filepath or title as identity. Do not emit a judge activation, a verdict, pressure, or a fulfillment activation; commit-gating is compiled postconditions plus render self-attestation. @@ -379,9 +380,10 @@ return write_result ``` Before forwarding to the compiler harness, the deterministic CLI preflights -the compile target source files for responsibility `id:` and required -`### Tools` sections, then resolves declared tools only within that target -(except `prose compile .`, which preserves whole-root preflight). After this +the compile target source files for well-formed `id:` frontmatter where present +and required `### Tools` sections, then resolves declared tools only within +that target (except `prose compile .`, which preserves whole-root preflight). +After this program returns, the CLI validates the written manifest. That host validation is the final guardrail; the compiler program should still treat `ir-v0.md` as binding before it writes. diff --git a/skills/open-prose/compiler/ir-v0.md b/skills/open-prose/compiler/ir-v0.md index 151c52b1..a487f3dd 100644 --- a/skills/open-prose/compiler/ir-v0.md +++ b/skills/open-prose/compiler/ir-v0.md @@ -24,7 +24,7 @@ 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 -(`world-model.md` §3; `architecture.md` §3.3). +(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 @@ -67,12 +67,12 @@ absolute segments. ## Fingerprints A **fingerprint** is a string token that changes if and only if the -semantically-material content changed (`world-model.md` §3). 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. +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 (`world-model.md` §4): the +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; @@ -95,14 +95,14 @@ values: `responsibility`, `function`, `gateway`, `pattern`, `test`, `unknown`. 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 (`plan.md` §3; `architecture.md` §7.1). A `kind: function` is a +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 (`architecture.md` §6.3, §3.1). It is a maintained truth like any +contract set (`forme.md`). It is a maintained truth like any other. The reconciler reads `edges` to resolve propagation targets. ```json @@ -129,17 +129,39 @@ other. The reconciler reads `edges` to resolve propagation targets. ### nodes Each node is one mounted producer (a `responsibility` or `gateway`). Required -fields: `node` (the node identity — its stable name), `contract_fingerprint` -(the frozen fingerprint of its contract/source), and `wake_source`. +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` -(`world-model.md` §5): 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`. +`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: @@ -150,34 +172,33 @@ 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 `producer`s; each adds a slot to the -subscriber's input tuple (`architecture.md` §3.1). Edges are not a step list and -carry no ordering; propagation order falls out of the DAG. +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 (`world-model.md` §5). Every entry point must be a +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` -(`architecture.md` §3.1). 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. +`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 (`architecture.md` §3.2, §1). `canonicalizer(world-model) → +its own receipt. `canonicalizer(world-model) → fingerprints`. ### The `####`-part → facet lowering (the named-parts rule) @@ -185,9 +206,8 @@ fingerprints`. 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` (`architecture.md` §3.2 L154–L171, "a `####` -sub-heading inside `### Maintains` is a facet; its body describes that part's -fields and which are material"; `delta.md` Part G L576–L579). The lowering is: +paths are that facet's `paths` (the named-parts rule in `contract-markdown.md`). +The lowering is: - Each `#### ` part → one facet `` whose fingerprint is computed over that part's **material** field paths. Materiality and normalization (text/sets/ @@ -197,11 +217,10 @@ fields and which are material"; `delta.md` Part G L576–L579). The lowering is: - 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 (`architecture.md` §3.2 L194–L197, "The shared `name` / - `last_corroborated` sit outside any part, so they move only the atomic token"). + 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 (`architecture.md` §3.2 L171). This is + 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[]` @@ -222,8 +241,7 @@ emits, atomic always included. 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 -(`architecture.md` §3.2 L173–L197, the worked competitor-activity-monitor -example). +(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` @@ -233,16 +251,30 @@ Required fields: `node` (a node id present in `topology.nodes`), `artifact` 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** (`architecture.md` §3.2; `world-model.md` §3): +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 +`/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 (`architecture.md` §3.3). There is no separate judge beat. +validators. There is no separate judge beat. ```json { @@ -268,15 +300,15 @@ 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 (`architecture.md` §6.1; `world-model.md` -§4). 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 (`architecture.md` §8: "schema -migration = a forced render"). +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 (`world-model.md` §4). +input_fingerprints)` — and nothing else is in the key (the memo key in +`concepts/reconciler.md`). ## Diagnostics @@ -298,7 +330,7 @@ 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 (`architecture.md` §3.1). +producers, is reported (the diagnostics step in `forme.md`). ## Compact Valid Example diff --git a/skills/open-prose/concepts/reconciler.md b/skills/open-prose/concepts/reconciler.md index 7cb9349e..a537a9e4 100644 --- a/skills/open-prose/concepts/reconciler.md +++ b/skills/open-prose/concepts/reconciler.md @@ -160,7 +160,7 @@ ledger — a node's durable memory. Its fields: | `semantic_diff` | render-input context ("3 controls went stale") — never a wake signal | | `prev` | pointer to the prior receipt (chains the ledger) | | `status` | `rendered` \| `skipped` \| `failed` | -| `cost` | mechanical token attribution — makes "cost scales with surprise" observable | +| `cost` | mechanical token attribution: `{ provider, model, tokens: { fresh, reused }, surprise_cause }`. `tokens.fresh` is what this render newly spent and `tokens.reused` what it recovered from prior work; `surprise_cause` names the wake source that caused the spend (`input` / `self` / `external`) and must equal `wake.source`. A `skipped` receipt carries zero cost. This is what makes "cost scales with surprise" observable | | `sig` | v1 meaning-layer attestation; the `signer` is an explicit null state | **Only `rendered` with a moved fingerprint propagates.** A `skipped` receipt diff --git a/skills/open-prose/contract-markdown.md b/skills/open-prose/contract-markdown.md index 12ff1df0..b51594bd 100644 --- a/skills/open-prose/contract-markdown.md +++ b/skills/open-prose/contract-markdown.md @@ -22,7 +22,8 @@ language: contracts, the world-model schema, runtime hints, and the render body. Every authored file is **one render** — a declaration plus the bounded session that runs it. The `kind` field is sugar over that single render atom: each kind -is the same render with different or missing sections (`plan.md` §1). +is the same render with different or missing sections (the render atom in +`concepts/reconciler.md`). The format optimizes for two readers: @@ -39,7 +40,7 @@ The format optimizes for two readers: of the standing truth it keeps current (`### Maintains`), and is woken over time. Mounting (a harness act) gives it identity, a persisted world-model, and resolved subscriptions. A responsibility is a node because it is mounted as a - subscribable producer — **not** because it holds state (`plan.md` §2). + subscribable producer — **not** because it holds state. - **Function** — a *called* render: the library tier, and the replacement for the retired `service`. A function is stateless and ephemeral. Its interface is @@ -65,7 +66,7 @@ The format optimizes for two readers: There is **no `system` kind**. Composition is imperative `call` *inside* a render (ProseScript `### Execution`) or a cross-node *subscription* across responsibilities (wired by Forme) — never a third "internally-autowired graph" -kind in the middle (`plan.md` §3). +kind in the middle. A run starts from the file the caller invokes, which is a responsibility, function, or gateway. @@ -197,7 +198,7 @@ responsibilities or functions without ambiguous parsing. Contract sections use Inside `### Maintains` and `### Requires`, a `####` sub-heading is **not** free-form documentation — it is a facet (a named part of the truth) or a facet-need (a named subscription to one). Everywhere else `####` is plain nested prose -(`architecture.md` §3.2 / §10.2; `delta.md` Part G). +(the named-parts rule under `## Maintains` below). ## Canonical Sections @@ -249,7 +250,7 @@ The judge-era responsibility vocabulary folds into the world-model model: | `### Services` / `### Wiring` | deleted with `system`; composition is `call` or subscription | `### Memory` is gone: one persisted world-model per node subsumes the old -reads/writes ledger (`world-model.md` §9.4). A `function` is stateless and has no +reads/writes ledger. A `function` is stateless and has no world-model, so it simply has no memory; a former `service`-with-memory that was genuinely stateful is really a `responsibility`, and its persisted state is its world-model. @@ -373,8 +374,9 @@ What outreach has been sent. Material: each sent contact and its evidence basis. Forme matches each `### Requires` facet-contract to the `### Maintains` facet that satisfies it semantically, across all mounted responsibilities, and draws -the subscription edge (`plan.md` §5). `### Requires` is the *need* (intent stays -with the human); the resolved producer is Forme's choice (mechanism). +the subscription edge (the wiring algorithm in `forme.md`). `### Requires` is +the *need* (intent stays with the human); the resolved producer is Forme's +choice (mechanism). Load `responsibility-runtime.md` and `concepts/responsibility.md` for the compile/run reconciler semantics. @@ -383,8 +385,7 @@ compile/run reconciler semantics. `### Maintains` declares the **shape** of the world-model — the schema, not the instance. It is not just a renamed `### Ensures`: a maintained truth is a -standing, typed, subscribable artifact, so its declaration does **four jobs** -(`world-model.md` §2): +standing, typed, subscribable artifact, so its declaration does **four jobs**: 1. **A type** — the fields and their shapes, including any freshness fields (`valid_until`, `last_corroborated`, `confidence`; see [Continuity](#continuity)). @@ -408,16 +409,17 @@ standing, typed, subscribable artifact, so its declaration does **four jobs** All four jobs live **inside** `### Maintains` — none gets its own block. The canonicalization spec and facet declarations may be written as semantically rich -natural language, as long as they are unambiguous, because the spec is **compiled -into a deterministic canonicalizer ahead of run time** (`world-model.md` §3). The -compiled canonicalizer travels with the contract, so a standalone render computes -its own fingerprints and signs a fingerprinted receipt with no harness present. +natural language, as long as they are unambiguous, because the spec is +**compiled into a deterministic canonicalizer ahead of run time** (the +canonicalizers section of `compiler/ir-v0.md`). The compiled canonicalizer +travels with the contract, so a standalone render computes its own fingerprints +and signs a fingerprinted receipt with no harness present. **The structured-backing rule.** Anything *subscribed* must have a structured, canonicalizable backing. Free-form rendered prose is a derived projection excluded from the fingerprint — otherwise an LLM re-rendering the same paragraph hashes differently every time and falsely re-triggers downstreams. Rule: fingerprint the -structured truth; render prose *from* it (`world-model.md` §3). The compiler lints +structured truth; render prose *from* it. The compiler lints subscribed fields that lack a structured backing. The world-model itself — the materialized truth the render writes and commits — @@ -431,20 +433,20 @@ simply by **naming the parts**: a `#### {name}` sub-heading inside `### Maintain **is** a facet, and its body describes that part's fields and which are material — in prose. Name no parts and the node has one truth: the **atomic facet**, the free default that costs nothing. Atomic-only — no `####` parts — is the v1 default and -the leaf-node case (`world-model.md` §9.5; `architecture.md` §10.2 records the -decision: *"a `####` sub-heading inside `### Maintains` declares a facet … Atomic-only -(no `####`) stays the default"*). +the leaf-node case; the `####`-part lowering in `compiler/ir-v0.md` says the same +thing from the compiler's side. The name an author writes is the **same name in three places at once** -(`architecture.md` §3.2, "the named-parts rule"; `delta.md` Part G): +(the named-parts rule): 1. **Fingerprint unit** — the compiled canonicalizer emits one token per `####` part, plus the always-on atomic token over the whole truth. A part moves only *its* token; fields that sit outside any part move only the atomic token. 2. **Subscription symbol** — a consumer names the part in `### Requires`, and the reconciler wakes that consumer only when *that* part's token moves. The join is - `Requires.` ↔ `Maintains.` (`architecture.md` §6.3: edges are - `subscriber.Requires.` → `producer.Maintains.`). + `Requires.` ↔ `Maintains.` (in the compiled topology of + `compiler/ir-v0.md`, an edge is `subscriber.Requires.` → + `producer.Maintains.`). 3. **World-model subtree** — the part is a named region of the content-addressed artifact, `published//…`, so "the directory structure *is* the state" shows the facets literally (`state/filesystem.md`). @@ -489,14 +491,48 @@ The symmetry is total: a producer's `#### funding` part under `### Maintains` is exactly the symbol a subscriber names in its `### Requires` (`Requires.funding` ↔ `Maintains.funding`). The memo key is unchanged — `(contract_fingerprint, input_fingerprints)`; facet granularity lives in *which* input-fingerprints a -subscriber consumes (one per subscribed facet), not in the key shape -(`delta.md` Part G). +subscriber consumes (one per subscribed facet), not in the key shape. + +### Facet families and per-entity mounts + +Some truths have one part *per entity* — one facet per starring user, per +inbound email, per agent session — and the entities are not known until run +time. The format has no grammar for enumerating them, and it does not need one: +the contract declares the **family**, and a harness instantiates the members. + +- **A placeholder heading declares a family.** `#### user:` inside + `### Maintains` declares one facet per entity: every member shares the + heading's shape and material boundary, and the entity is named at run time. + The angle-bracket token is the placeholder; the prefix (`user:`) keeps the + family distinct from any literal `####` part beside it. A fixed, known set of + entities may instead be enumerated as literal parts (`#### claude`, + `#### codex`, …); the two forms differ only in whether the author or the + harness names the members. +- **A placeholder in a facet-need subscribes to one member.** `session:` + in a `### Requires` entry names a member of the family, not the whole family; + the harness binds the member when it mounts the subscriber, so the summary + for `claudeA` never wakes on a `codexA` edit. A need that asks for *every* + member is the diamond rule's deliberate fan-in — one slot per member — and + should say so. +- **A bracketed title marks a per-entity mount.** `# Title [instance]` — as in + `# Runtime Adapter [runtime]` — says that a harness mounts this contract once + per entity, one adapter per runtime. Forme ignores the title as always; the + bracket is a cue to the reader and to the harness, and the per-mount node + name is the harness's to assign. + +The division of labor is the same as for any node: **the contract declares the +family; a harness instantiates facets and mounts.** The compiler emits the +family, never an enumeration — `src/` lists no instances, and a topology +produced by mounting `src/` alone carries one node per contract. Forme matches +a placeholder need against the family it names (`forme.md`, Step 2); the +per-entity fan-out an example's README describes is a harness's expansion of +that family, not a second copy of the contract. ## Continuity `### Continuity` is the node's **wake-source** declaration — *what can wake this node* — and is **intrinsic** to the responsibility: it travels with the contract, -not the mount (`plan.md` §4; `architecture.md` §4.2). It has three modes: +not the mount (the wake model in `concepts/reconciler.md`). It has three modes: - **input-driven** (the default) — woken by an upstream node's receipt whose subscribed facet-fingerprint moved. Falls out of `### Requires`, so it needs no @@ -509,16 +545,16 @@ not the mount (`plan.md` §4; `architecture.md` §4.2). It has three modes: - **external-driven** — a declared outside trigger (webhook / cron / manual kick). This is the `gateway` case; its input arrives from outside the graph. -Every wake is a receipt; the only variable is who emitted it (`world-model.md` -§5). `### Continuity` declares *which* sources may wake a node — it never makes the -wake decision intelligent; the reconciler stays dumb. +Every wake is a receipt; the only variable is who emitted it. `### Continuity` +declares *which* sources may wake a node — it never makes the wake decision +intelligent; the reconciler stays dumb. **Freshness — state vs. policy.** Freshness *state* (`valid_until`, `last_corroborated`, `confidence`) lives **in the world-model** as data declared by `### Maintains`. Freshness *policy* — the recheck cadence — lives in `### Continuity`. The bridge: a `valid_until` lapsing flips a fact's status, which moves that facet's fingerprint, so "time becoming material" is just another change -that propagates as surprise (`world-model.md` §6). `### Continuity` may *read* the +that propagates as surprise. `### Continuity` may *read* the world-model's soonest `valid_until` to drive a data-driven recheck cadence, but the cadence rule stays in `### Continuity` and the expiry data stays in the world-model. @@ -554,7 +590,7 @@ let s = call summarizer You author functions rarely and call them constantly; most ship pre-built in `std/`. They are the standard-library tier — the place the "unmounted" render -actually lives (`plan.md` §3). +actually lives. ## Patterns @@ -752,11 +788,14 @@ or discuss, it should usually be a `###` section. A `kind: test` file also declares `subject:` to name the responsibility or function it runs. -A `kind: responsibility` file also declares required `id:` frontmatter to name the -stable Markdown identity for the responsibility. The id is generated once by -tooling as a UUIDv7-compatible 16-byte value, rendered as uppercase Crockford -base32, and preserved across filename and `name:` renames. The slug is display; -`id:` is identity. +A `kind: responsibility` or `kind: gateway` file may declare `id:` frontmatter to +give the contract an identity that survives filename and `name:` renames. Without +one, the slug is the identity. An id is a UUIDv7-compatible 16-byte value rendered +as 26 characters of uppercase Crockford base32 (`0-9A-HJKMNP-TV-Z`), minted once +by `scripts/mint-contract-id.mjs` in the language repository and never hand-typed. + +`version:` is optional, author-owned provenance in semver form. The compiler +ignores it; it is not the skill version. ## Contract Item Style @@ -816,7 +855,7 @@ return research `### Execution` is the intra-node render body: `call` (invoke a function), `session` / `agent` / `resume` (spawn ephemeral sub-agents), plus control flow. All of it is internal to producing this node's world-model, and **none of it is a -node** (`plan.md` §7). Cross-node connection is only ever a subscription. When +node**. Cross-node connection is only ever a subscription. When `### Execution` is present, Forme validates contracts and extracts the call graph, but the Prose VM follows the written order. diff --git a/skills/open-prose/examples/README.md b/skills/open-prose/examples/README.md index a2573c14..4f2c6a7f 100644 --- a/skills/open-prose/examples/README.md +++ b/skills/open-prose/examples/README.md @@ -178,4 +178,7 @@ Eleven examples carry a native run block (a `## Quick Start` or `## Run it` with content-performance-loop, customer-risk-radar, declared-skills, declared-tools, incident-briefing-room, release-readiness, research-inbox-triage, stargazer-outreach, and vendor-renewal-watch. The remaining examples are -conformance corpora whose expanded topologies are produced by a harness. +described topologies: mounting `src/` alone yields only the contracts an example +authors, and where a README states a node or edge count that `src/` cannot mount +by itself, that count is what the reference harness's expansion produces (see +the Harnesses section of the repository README). diff --git a/skills/open-prose/examples/agent-observatory/README.md b/skills/open-prose/examples/agent-observatory/README.md index ac81cabc..7b972d0e 100644 --- a/skills/open-prose/examples/agent-observatory/README.md +++ b/skills/open-prose/examples/agent-observatory/README.md @@ -17,8 +17,9 @@ meta-generator as a standing node, and dual MD + HTML artifacts. ## The DAG (14 nodes / 22 edges) -`src/` ships the 9 authored contracts; the 14-node / 22-edge topology is what a -harness's expansion produces from them. +`src/` ships the 9 authored contracts and mounts 9 nodes on its own; the +14-node / 22-edge topology is what the reference harness's per-entity expansion +produces from them (see the Harnesses section of the repository README). ```text Agent FS (external) diff --git a/skills/open-prose/examples/agent-observatory/src/runtime-adapter.prose.md b/skills/open-prose/examples/agent-observatory/src/runtime-adapter.prose.md index 2da895f0..78255f1f 100644 --- a/skills/open-prose/examples/agent-observatory/src/runtime-adapter.prose.md +++ b/skills/open-prose/examples/agent-observatory/src/runtime-adapter.prose.md @@ -14,10 +14,12 @@ version: 0.15.0 ### Requires -- the `runtime` facet of `runtime-watch` (NOT `@atomic`) — the adapter for - `codex` wakes on `codex` only; a Claude change leaves it dark. This selective - subscription is the dark lane: the gateway moved one facet, so exactly one - adapter lane lights. +- this runtime's `` facet of `runtime-watch` (NOT `@atomic`) — one of + the gateway's per-runtime parts (`claude`, `codex`, `opencode`, `pi`), bound + to this mount's runtime by the harness. The adapter for `codex` wakes on + `codex` only; a Claude change leaves it dark. This selective subscription is + the dark lane: the gateway moved one facet, so exactly one adapter lane + lights. ### Maintains @@ -30,10 +32,21 @@ The normalized sessions for this runtime, as the truth the session-ledger merges Parse only the changed append range when the format supports it; large transcripts do not require full re-summarization. -**Canonicalization spec**: the adapter exposes its whole truth as `@atomic` (it -has no named facets of its own). A facet-less producer subscribes via the -exported `@atomic` token — never a `"*"` wildcard, which would silently never -propagate. +Each mount publishes its truth under one member of a facet family, so the +ledger can fan in over every runtime by name and its receipt shows which +runtime moved: + +#### runtime: + +The normalized sessions of exactly one runtime — `runtime:codex` on the codex +mount, `runtime:claude` on the Claude mount. Material: the session set keyed by +session id, and each session's `rev`, `normalized_head`, and `workstream`; +parse timestamps and file paths are immaterial. The session-ledger subscribes +to every member of this family. + +**Canonicalization spec**: the whole truth is also exposed as `@atomic`, as +always. A facet-less subscriber reads the exported `@atomic` token — never a +`"*"` wildcard, which would silently never propagate. ### Continuity diff --git a/skills/open-prose/examples/agent-observatory/src/runtime-watch.prose.md b/skills/open-prose/examples/agent-observatory/src/runtime-watch.prose.md index 645c0b70..6fcf3bc9 100644 --- a/skills/open-prose/examples/agent-observatory/src/runtime-watch.prose.md +++ b/skills/open-prose/examples/agent-observatory/src/runtime-watch.prose.md @@ -22,16 +22,14 @@ bytes does not move any fingerprint, so the whole graph below memo-skips and the cost meter stays flat. This is the point: a cheap gateway can watch every runtime and the expensive synthesis only wakes when some session state actually changed. -### Watches - -- `~/.claude/projects/**/*.jsonl`, `~/.claude/tasks/**/*` -- `~/.codex/sessions/**/*`, `~/.codex/archived_sessions/**/*` -- `~/.opencode/**/*` -- `~/.pi/agent/sessions/**/*` - ### Receives -- file path, mtime, size, content-hash (or append-range hash) +- The watched roots, scanned on a schedule or by a filesystem watcher: + - `~/.claude/projects/**/*.jsonl`, `~/.claude/tasks/**/*` + - `~/.codex/sessions/**/*`, `~/.codex/archived_sessions/**/*` + - `~/.opencode/**/*` + - `~/.pi/agent/sessions/**/*` +- Per changed file: path, mtime, size, content-hash (or append-range hash) - `runtime`: one of `claude`, `codex`, `opencode`, `pi` ### Maintains diff --git a/skills/open-prose/examples/agent-observatory/src/session-ledger.prose.md b/skills/open-prose/examples/agent-observatory/src/session-ledger.prose.md index 068b108c..88ae9694 100644 --- a/skills/open-prose/examples/agent-observatory/src/session-ledger.prose.md +++ b/skills/open-prose/examples/agent-observatory/src/session-ledger.prose.md @@ -14,9 +14,10 @@ version: 0.15.0 ### Requires -- `adapter-claude`, `adapter-codex`, `adapter-opencode`, `adapter-pi` - (each via `@atomic`) — a fan-in over all runtimes. Only the adapter that - actually moved contributes a change; the others reuse their prior truth. +- every `runtime:` facet of `runtime-adapter` — a deliberate fan-in over + the whole family, one slot per mounted runtime (`claude`, `codex`, `opencode`, + `pi`). Only the adapter that actually moved contributes a change; the others + reuse their prior truth. ### Maintains @@ -52,8 +53,7 @@ unchanged ledger moves no session facet. ### Continuity -- input-driven: a moved truth on any of the four runtime adapters - (`adapter-claude`, `adapter-codex`, `adapter-opencode`, `adapter-pi`) wakes - this fan-in merge. +- input-driven: a moved `runtime:` facet on any of the four runtime + adapters wakes this fan-in merge. - This is an incremental merge; it does not re-derive every historical session on each file change. diff --git a/skills/open-prose/examples/auto-pocock/src/auto-pocock.prose.md b/skills/open-prose/examples/auto-pocock/src/auto-pocock.prose.md index 71d8c2f8..7de9ec51 100644 --- a/skills/open-prose/examples/auto-pocock/src/auto-pocock.prose.md +++ b/skills/open-prose/examples/auto-pocock/src/auto-pocock.prose.md @@ -21,7 +21,7 @@ This workflow is a sequential pipeline, so it flattens into a single called `function` whose `### Execution` drives the steps in order. Each former service is now a `function` this render `call`s. Because order matters end-to-end, the choreography is imperative ProseScript rather than a wired -DAG (`plan.md` §7). +DAG. Names, vocabulary, and template structure are credited to Pocock and referenced verbatim against the public `mattpocock/skills` repo wherever diff --git a/skills/open-prose/examples/basic-unit-suite/src/alert-state.prose.md b/skills/open-prose/examples/basic-unit-suite/src/alert-state.prose.md index fea9504a..3c3b41f3 100644 --- a/skills/open-prose/examples/basic-unit-suite/src/alert-state.prose.md +++ b/skills/open-prose/examples/basic-unit-suite/src/alert-state.prose.md @@ -33,13 +33,14 @@ iff a positive total below threshold, else `quiet`. Self-policed before signing. Read `CountSummary` by reference, map it onto a status, and commit. If the read or mapping fails, sign a failure receipt and leave the prior `AlertState` untouched. -### Failure containment - -If the render fails after reading the summary, the harness signs a **failure -receipt** (status `failed`, zero fresh tokens) and commits nothing. The last -`rendered` `AlertState` stays active, `Executive Snapshot` reads that prior truth -by reference, and a later retry resumes from it — the failure is visible and -auditable without corrupting the world-model. +### Invariants + +- A render that fails after reading the summary commits nothing: the harness + signs a **failure receipt** (status `failed`, zero fresh tokens) and the last + `rendered` `AlertState` stays active. +- No downstream node ever consumes a partial output. `Executive Snapshot` reads + the prior truth by reference, and a later retry resumes from it — the failure + is visible and auditable without corrupting the world-model. ### Continuity diff --git a/skills/open-prose/examples/basic-unit-suite/src/counter-events.prose.md b/skills/open-prose/examples/basic-unit-suite/src/counter-events.prose.md index 5a0b3847..e37540f5 100644 --- a/skills/open-prose/examples/basic-unit-suite/src/counter-events.prose.md +++ b/skills/open-prose/examples/basic-unit-suite/src/counter-events.prose.md @@ -13,10 +13,13 @@ version: 0.15.0 ### Continuity -external-driven +- external-driven: a webhook, a poll, or a manual kick becomes one external wake + at the system's edge. +- self-driven: a weekday 09:00 self-kick may re-scan even when no webhook fires; + a byte-identical re-scan memo-skips, so the self-kick costs nothing when + nothing changed. -A webhook, a poll, or a manual kick becomes one external wake at the system's -edge. The gateway folds each accepted counter event into the canonical ledger and +The gateway folds each accepted counter event into the canonical ledger and projects two **independent facets** so a downstream subscriber wakes only on the slice it actually depends on (U05). Replaying the same event id is a no-op — the ledger dedups by id, so a re-delivery produces a byte-identical world-model and @@ -61,8 +64,3 @@ subscribes here. Forme keys the wake on the producing node; the subscribers above resolve their edges to this gateway's `counts` / `raw_events` facets. - -### Continuity recheck - -A weekday 09:00 self-kick may re-scan even when no webhook fires; a byte-identical -re-scan memo-skips, so the self-kick costs nothing when nothing changed. diff --git a/skills/open-prose/examples/competitor-activity/README.md b/skills/open-prose/examples/competitor-activity/README.md index 90e008b3..d850df3a 100644 --- a/skills/open-prose/examples/competitor-activity/README.md +++ b/skills/open-prose/examples/competitor-activity/README.md @@ -11,10 +11,11 @@ cp dist/manifest.next.json dist/manifest.active.json # promote the compiled IR prose serve ``` -`prose serve` then waits for a subscribed feed to publish: the monitor's -`funding-signals`, `hiring-signals`, and `launch-signals` upstreams are not part -of `src/`, so nothing renders until a node that maintains them is mounted (the -6h self-driven re-check only re-derives truth those inputs already produced). +`prose serve` then waits for the `signal-feeds` gateway to fire: a webhook +delivery or the 6h feed poll that carries a funding, hiring, or launch item the +monitor has not seen. A re-poll that returns the same items moves no facet, so +nothing downstream renders (the monitor's own 6h self-driven re-check only +re-derives truth those inputs already produced). ## What This Repository Does @@ -27,7 +28,7 @@ each as its own subscribable facet. `src/competitor-activity-monitor.prose.md` declares its facets by **naming the parts** of its truth: a `####` sub-heading inside `### Maintains` _is_ a facet. The author writes one name and gets three things at once -(`architecture.md` §3.2, the named-parts rule): +(the named-parts rule in `contract-markdown.md`): - the **fingerprint unit**: the compiled canonicalizer emits one token per `####` part, plus the always-on `@atomic` token over the whole truth; @@ -37,16 +38,21 @@ The author writes one name and gets three things at once - the **world-model subtree**: `published//…`, so the on-disk directory structure literally shows the facets (`state/filesystem.md`). -A downstream that `### Requires` `funding-signals` and resolves to the -`#### funding` facet wakes only when funding moves, not when `#### hiring` or -`#### product-launches` move. The shared `name` / `last_corroborated` sit outside +A downstream that `### Requires` the monitor's funding truth resolves to the +`#### funding` facet and wakes only when funding moves, not when `#### hiring` +or `#### product-launches` move. The monitor itself subscribes the same way one +level up: its three needs resolve, facet by facet, to the `signal-feeds` +gateway's `#### funding-signals`, `#### hiring-signals`, and +`#### launch-signals` parts. The shared `name` / `last_corroborated` sit outside any part, so they move only the `@atomic` token. This is React's selector -boundary made authorable (`world-model.md` §3, "Declaring facets"). +boundary made authorable. ## Source Shape -- `src/`: the `competitor-activity-monitor` responsibility with three `####` - facet parts under `### Maintains` +- `src/`: the `signal-feeds` gateway (three `####` signal facets, the entry + point) and the `competitor-activity-monitor` responsibility that subscribes to + them facet by facet and declares three `####` facet parts of its own under + `### Maintains` - `dist/`: compiled topology + canonicalizers produced by `prose compile` - `runs/`: append-only receipt ledger - `state/`: the canonical world-model, laid out as `published//…` subtrees diff --git a/skills/open-prose/examples/competitor-activity/src/competitor-activity-monitor.prose.md b/skills/open-prose/examples/competitor-activity/src/competitor-activity-monitor.prose.md index 6313a30f..31de7d7c 100644 --- a/skills/open-prose/examples/competitor-activity/src/competitor-activity-monitor.prose.md +++ b/skills/open-prose/examples/competitor-activity/src/competitor-activity-monitor.prose.md @@ -12,7 +12,7 @@ id: 067NC4KG01RG50R40M30E20918 > independently-subscribable facets — `#### funding`, `#### hiring`, and > `#### product-launches` — so a downstream that watches funding wakes only when > funding moves, not when hiring or launches move. This is React's selector -> boundary made authorable (`architecture.md` §3.2, the named-parts rule). +> boundary made authorable (the named-parts rule). ### Goal @@ -22,14 +22,16 @@ A current, corroborated view of each tracked competitor's material activity. Subscription contracts — Forme matches each entry to a producing node's `### Maintains` facet (`Requires. ↔ Maintains.`), and run time -follows the resolved input-fingerprint tuple. +follows the resolved input-fingerprint tuple. All three resolve to the +`signal-feeds` gateway, facet by facet: Forme draws one edge per facet, so the +input tuple carries one slot per signal and the receipt shows which one moved. -- `funding-signals`: a current view of competitor funding events. - *(A funding feed/gateway maintains this.)* -- `hiring-signals`: a current view of competitor hiring activity. - *(A hiring/jobs feed maintains this.)* -- `launch-signals`: a current view of announced or shipped competitor products. - *(A product/press feed maintains this.)* +- `funding-signals`: a current view of competitor funding events — the + `#### funding-signals` facet of `signal-feeds`. +- `hiring-signals`: a current view of competitor hiring activity — the + `#### hiring-signals` facet of `signal-feeds`. +- `launch-signals`: a current view of announced or shipped competitor products + — the `#### launch-signals` facet of `signal-feeds`. ### Maintains @@ -65,7 +67,7 @@ on funding or launch moves. Announced or shipped products per competitor. Material: the launch set (unordered); a ship-date slipping past today flips each launch's `shipped` status, which is material — so "time becoming material" propagates as an ordinary -fingerprint move (`world-model.md` §6). +fingerprint move. **Postconditions** (self-policed by the render before it signs — no separate judge beat): diff --git a/skills/open-prose/examples/competitor-activity/src/signal-feeds.prose.md b/skills/open-prose/examples/competitor-activity/src/signal-feeds.prose.md new file mode 100644 index 00000000..96433c97 --- /dev/null +++ b/skills/open-prose/examples/competitor-activity/src/signal-feeds.prose.md @@ -0,0 +1,72 @@ +--- +name: signal-feeds +kind: gateway +version: 0.18.0 +--- + +# Signal Feeds + +> The monitor's upstream: one gateway that brings the three external competitor +> signals into the graph as three independently-subscribable facets. It has no +> `### Requires` (its input arrives from outside the graph), it `### Maintains` +> the latest normalized feed truth, and its `### Continuity` is +> **external-driven**, which is how Forme finds it as the DAG entry point. The +> facets are the point: a funding item moves only `#### funding-signals`, so +> the edge the monitor's funding need resolved to is the one that carries it. + +### Continuity + +- external-driven + +### Schedule + +- Every 6h, poll each configured feed; a webhook delivery may arrive at any + time between polls. + +### Receives + +- Funding: press releases and filings — `competitor`, `round`, `amount`, + `date`, `source` +- Hiring: job boards and careers pages — `competitor`, `department`, `role`, + `posted_at`, `source` +- Launches: product and press feeds — `competitor`, `product`, `announced_at`, + `ship_date`, `source` +- A delivery id or polling cursor — the dedupe / high-water key + +### Maintains + +The latest normalized feed items, keyed by `competitor_id` — the raw signals the +monitor corroborates into its standing view. Delivery ids, polling cursors, and +`fetched_at` are immaterial everywhere; a re-poll that returns the same items +moves no facet, so the monitor memo-skips and the cost meter stays flat. Each +`####` part below is a facet: its name is the fingerprint unit, the subscription +symbol the monitor names in `### Requires`, and the `published//…` +subtree. + +#### funding-signals + +Funding events per competitor as reported by the feeds. Material: the event set +(unordered) and each event's round, amount, date, and source. The monitor's +`funding-signals` need resolves here. + +#### hiring-signals + +Open roles per competitor as reported by the feeds. Material: the posting set +(unordered) and each posting's department, role, and source; `posted_at` is +material only to the day. The monitor's `hiring-signals` need resolves here. + +#### launch-signals + +Announced or shipped products per competitor as reported by the feeds. +Material: the launch set (unordered) and each launch's product, announced date, +ship date, and source. The monitor's `launch-signals` need resolves here. + +### Emits + +- competitor-activity-monitor + +### Payload + +Pass the new or changed feed items grouped by signal kind, each with its source +URL, as the incoming truth. A full poll page and a single webhook delivery are +both valid shapes. diff --git a/skills/open-prose/examples/feedback-pulse/README.md b/skills/open-prose/examples/feedback-pulse/README.md index 4ab868dc..8fbfc5c6 100644 --- a/skills/open-prose/examples/feedback-pulse/README.md +++ b/skills/open-prose/examples/feedback-pulse/README.md @@ -63,8 +63,10 @@ This is a different audience (product feedback) and a different graph shape ``` 7 nodes / 11 edges. `gateway.feedback-inbox` is the single entry point; the graph -is acyclic. `src/` ships the 4 authored contracts; the 7-node / 11-edge topology -is what a harness's expansion produces from them. +is acyclic. `src/` ships the 4 authored contracts and mounts 4 nodes on its own; +the 7-node / 11-edge topology is what the reference harness's per-entity +expansion produces from them (see the Harnesses section of the repository +README). ## Conformance expectations diff --git a/skills/open-prose/examples/forme-fixpoint/src/contract-registry.prose.md b/skills/open-prose/examples/forme-fixpoint/src/contract-registry.prose.md index 96d2b0a8..61abee8e 100644 --- a/skills/open-prose/examples/forme-fixpoint/src/contract-registry.prose.md +++ b/skills/open-prose/examples/forme-fixpoint/src/contract-registry.prose.md @@ -2,7 +2,6 @@ name: contract-registry kind: responsibility version: 0.15.0 -id: contract-registry --- # Contract Registry diff --git a/skills/open-prose/examples/forme-fixpoint/src/contract-source-files.prose.md b/skills/open-prose/examples/forme-fixpoint/src/contract-source-files.prose.md index 5faec1b3..bf295fea 100644 --- a/skills/open-prose/examples/forme-fixpoint/src/contract-source-files.prose.md +++ b/skills/open-prose/examples/forme-fixpoint/src/contract-source-files.prose.md @@ -2,7 +2,6 @@ name: contract-source-files kind: gateway version: 0.15.0 -id: contract-source-files --- # Contract Source Files @@ -34,7 +33,9 @@ immaterial edit (a reflowed comment, trailing whitespace) leaves `content_fingerprint` — and therefore `source_set_fingerprint` — unmoved, so the registry downstream memo-skips. -### Continuity: external-driven +### Continuity + +- external-driven External file watcher or scheduled scan. This gateway is an **entry point**: wake when contract source content changes. It has no `### Requires` — its truth diff --git a/skills/open-prose/examples/forme-fixpoint/src/operator-pins.prose.md b/skills/open-prose/examples/forme-fixpoint/src/operator-pins.prose.md index ecceb3c3..038531cd 100644 --- a/skills/open-prose/examples/forme-fixpoint/src/operator-pins.prose.md +++ b/skills/open-prose/examples/forme-fixpoint/src/operator-pins.prose.md @@ -2,7 +2,6 @@ name: operator-pins kind: gateway version: 0.15.0 -id: operator-pins --- # Operator Pins @@ -29,7 +28,9 @@ OperatorPinLedger { } ``` -### Continuity: external-driven +### Continuity + +- external-driven External. This gateway is an **entry point**: wake when an operator resolves ambiguity or overrides topology. No `### Requires`. diff --git a/skills/open-prose/examples/forme-fixpoint/src/schedule-plan.prose.md b/skills/open-prose/examples/forme-fixpoint/src/schedule-plan.prose.md index d8f3ca0e..2ad20460 100644 --- a/skills/open-prose/examples/forme-fixpoint/src/schedule-plan.prose.md +++ b/skills/open-prose/examples/forme-fixpoint/src/schedule-plan.prose.md @@ -2,7 +2,6 @@ name: schedule-plan kind: responsibility version: 0.15.0 -id: schedule-plan --- # Schedule Plan Projection diff --git a/skills/open-prose/examples/forme-fixpoint/src/topology-change-reporter.prose.md b/skills/open-prose/examples/forme-fixpoint/src/topology-change-reporter.prose.md index ecd27a92..4e6d7d17 100644 --- a/skills/open-prose/examples/forme-fixpoint/src/topology-change-reporter.prose.md +++ b/skills/open-prose/examples/forme-fixpoint/src/topology-change-reporter.prose.md @@ -2,7 +2,6 @@ name: topology-change-reporter kind: responsibility version: 0.15.0 -id: topology-change-reporter --- # Topology Change Reporter diff --git a/skills/open-prose/examples/forme-fixpoint/src/topology-maintainer.prose.md b/skills/open-prose/examples/forme-fixpoint/src/topology-maintainer.prose.md index 59a57324..c5c6fd51 100644 --- a/skills/open-prose/examples/forme-fixpoint/src/topology-maintainer.prose.md +++ b/skills/open-prose/examples/forme-fixpoint/src/topology-maintainer.prose.md @@ -2,7 +2,6 @@ name: topology-maintainer kind: responsibility version: 0.15.0 -id: topology-maintainer --- # Topology Maintainer (Forme) diff --git a/skills/open-prose/examples/forme-fixpoint/src/topology-safety-auditor.prose.md b/skills/open-prose/examples/forme-fixpoint/src/topology-safety-auditor.prose.md index 42a7ddfc..31bd165e 100644 --- a/skills/open-prose/examples/forme-fixpoint/src/topology-safety-auditor.prose.md +++ b/skills/open-prose/examples/forme-fixpoint/src/topology-safety-auditor.prose.md @@ -2,7 +2,6 @@ name: topology-safety-auditor kind: responsibility version: 0.15.0 -id: topology-safety-auditor --- # Topology Safety Auditor diff --git a/skills/open-prose/examples/github-star-enricher/src/human-review-events.prose.md b/skills/open-prose/examples/github-star-enricher/src/human-review-events.prose.md index 7597aca4..e7eab43b 100644 --- a/skills/open-prose/examples/github-star-enricher/src/human-review-events.prose.md +++ b/skills/open-prose/examples/github-star-enricher/src/human-review-events.prose.md @@ -12,7 +12,9 @@ version: 0.15.0 > review-ledger truth that the `registry` and every `outreach-packet` subscribe > to; its `### Continuity` is **external-driven**. -### Continuity: external-driven +### Continuity + +- external-driven The system never sends outreach on its own. A packet only advances past `ready_for_review` when a *human* acts — and that action arrives as an external diff --git a/skills/open-prose/examples/github-star-enricher/src/star-events.prose.md b/skills/open-prose/examples/github-star-enricher/src/star-events.prose.md index 08259042..2538d201 100644 --- a/skills/open-prose/examples/github-star-enricher/src/star-events.prose.md +++ b/skills/open-prose/examples/github-star-enricher/src/star-events.prose.md @@ -12,7 +12,9 @@ version: 0.15.0 > subscribes to, and its `### Continuity` is **external-driven**, which is how > Forme finds it as a DAG entry point. -### Continuity: external-driven +### Continuity + +- external-driven A GitHub star webhook, or a scheduled poll of the stargazers API, translates into a *receipt* at the edge of the system — one wake event type, an external source. diff --git a/skills/open-prose/examples/implementation-pipeline/README.md b/skills/open-prose/examples/implementation-pipeline/README.md index e0c6fa7b..8239b0fa 100644 --- a/skills/open-prose/examples/implementation-pipeline/README.md +++ b/skills/open-prose/examples/implementation-pipeline/README.md @@ -21,8 +21,9 @@ lanes; verification and a report/signpost index close it out. - Work the six fixed lanes cannot own becomes `unassigned_work` on the work-plan's own truth (**never a 7th mounted node**). The expanded topology is frozen at - **16 nodes**. `src/` ships the 6 authored contracts; the 16-node topology is - what a harness's expansion produces from them. + **16 nodes**. `src/` ships the 6 authored contracts and mounts 6 nodes on its + own; the 16-node topology is what the reference harness's per-lane expansion + produces from them (see the Harnesses section of the repository README). - A change to ONE lane's contents lights **one lane**; the five siblings stay dark (independent per-lane facet tokens). - A change to the **foundation** fans out to **all six lanes once**: the @@ -72,7 +73,7 @@ ingress edge, not a node. ## Conformance expectations -A conforming harness proves the expanded topology stays at 16 nodes, extra work remains +A conforming harness proves the expanded topology never grows, extra work remains `unassigned_work`, lane-local changes wake one lane, foundation changes wake all six lanes once, rejected work never integrates, and quiet replay adds no fresh cost. diff --git a/skills/open-prose/examples/implementation-pipeline/src/construction-review.prose.md b/skills/open-prose/examples/implementation-pipeline/src/construction-review.prose.md index 13fef4f7..0736050c 100644 --- a/skills/open-prose/examples/implementation-pipeline/src/construction-review.prose.md +++ b/skills/open-prose/examples/implementation-pipeline/src/construction-review.prose.md @@ -40,12 +40,17 @@ The world-model schema — the review verdict. `{ accepted_lanes, rejected_lanes }`, so the integration node wakes when the accept/reject SET changes, not on cosmetic churn. -### Facets +**Facets** — one named part, the verdict the integration node subscribes to. +Everything outside it (`cross_lane_conflicts`, `missing_tests`, +`export_requests`, `open_issues`, `ready_for_integration`) moves only the +atomic token. #### accepted The accept/reject verdict the `integration-builder` subscribes to. A rejected lane appears here with its reason and is excluded downstream by construction. +Material: the `accepted_lanes` set and the `rejected_lanes` set, each rejection +with its `lane` and `reason`; immaterial: the order the lanes were reviewed in. ### Continuity diff --git a/skills/open-prose/examples/implementation-pipeline/src/foundation-builder.prose.md b/skills/open-prose/examples/implementation-pipeline/src/foundation-builder.prose.md index 175b617f..22007c66 100644 --- a/skills/open-prose/examples/implementation-pipeline/src/foundation-builder.prose.md +++ b/skills/open-prose/examples/implementation-pipeline/src/foundation-builder.prose.md @@ -38,13 +38,17 @@ The world-model schema — the shared foundation the lanes conform to. It moves when a canonical shape changes (e.g. `Receipt@v1` → `Receipt@v2`); that single move is the fanout that wakes every lane. -### Facets +**Facets** — one named part, the fanout spine. `vocabulary`, `deletion_list`, +`migration_rules`, and `notes_for_lanes` sit outside it and move only the atomic +token, so a note edit never wakes a lane. #### shared-shapes The canonical shapes + invariants the lanes conform to. This is the fanout spine: every construction lane subscribes to this facet, so when it moves, all six lanes -wake exactly once — the intentional, auditable blast radius. +wake exactly once — the intentional, auditable blast radius. Material: the +canonical shape set (each shape's name and version, e.g. `Receipt@v2`) and the +invariant list; immaterial: the wording of a shape's description. ### Continuity diff --git a/skills/open-prose/examples/implementation-pipeline/src/implementation-work-plan.prose.md b/skills/open-prose/examples/implementation-pipeline/src/implementation-work-plan.prose.md index 088d527e..437a72fc 100644 --- a/skills/open-prose/examples/implementation-pipeline/src/implementation-work-plan.prose.md +++ b/skills/open-prose/examples/implementation-pipeline/src/implementation-work-plan.prose.md @@ -47,41 +47,51 @@ own. A change to one lane's contents moves ONLY that lane's facet; the five sibling lane facets stay byte-identical, so the five sibling lanes never wake. `unassigned_work` + `ambiguous_work` move only the `diagnostics` facet. -### Facets - -Named parts of this truth. Each `####` part is a facet: its name is at once the -fingerprint unit, the subscription symbol (`Requires.` ↔ `Maintains.`), -and the published subtree. A lane subscribes to ONLY its own facet, so a move in -one lane does not wake a sibling lane. +**Facets** — the named parts of this truth. Each `####` part below is a facet: +its name is at once the fingerprint unit, the subscription symbol +(`Requires.` ↔ `Maintains.`), and the published subtree. A lane +subscribes to ONLY its own facet, so a move in one lane does not wake a sibling +lane. Every lane part shares one material boundary: the set of work-item ids +assigned to that lane, each item's owned paths and expected tests, and its +cross-lane dependencies. Item ordering and wording-only edits to an item's +source document are immaterial everywhere. #### lane:sdk-world-model -The items assigned to the SDK World-Model construction lane. +The items assigned to the SDK World-Model construction lane. Material: this +lane's assigned item set with owned paths, expected tests, and dependencies. #### lane:sdk-runtime -The items assigned to the SDK Runtime construction lane. +The items assigned to the SDK Runtime construction lane. Material: this lane's +assigned item set with owned paths, expected tests, and dependencies. #### lane:sdk-compile -The items assigned to the SDK Compile construction lane. +The items assigned to the SDK Compile construction lane. Material: this lane's +assigned item set with owned paths, expected tests, and dependencies. #### lane:skill-contract -The items assigned to the Skill Contract construction lane. +The items assigned to the Skill Contract construction lane. Material: this +lane's assigned item set with owned paths, expected tests, and dependencies. #### lane:examples-tests -The items assigned to the Examples/Test construction lane. +The items assigned to the Examples/Test construction lane. Material: this lane's +assigned item set with owned paths, expected tests, and dependencies. #### lane:docs-signposts -The items assigned to the Docs/Signpost construction lane. +The items assigned to the Docs/Signpost construction lane. Material: this lane's +assigned item set with owned paths, expected tests, and dependencies. #### diagnostics `unassigned_work` and `ambiguous_work` — the overflow surface. Extra work the six -fixed lanes cannot cover is recorded HERE, never as a mounted node. +fixed lanes cannot cover is recorded HERE, never as a mounted node. Material: the +unassigned item set and the ambiguous item set, each item with the reason it +could not be placed; immaterial: the order the items were discovered in. ### Continuity diff --git a/skills/open-prose/examples/implementation-pipeline/src/planning-corpus.prose.md b/skills/open-prose/examples/implementation-pipeline/src/planning-corpus.prose.md index 58f1c145..e7564d57 100644 --- a/skills/open-prose/examples/implementation-pipeline/src/planning-corpus.prose.md +++ b/skills/open-prose/examples/implementation-pipeline/src/planning-corpus.prose.md @@ -36,33 +36,29 @@ docs-only edit never perturbs the repo or config lanes downstream. ### Maintains The latest incoming planning truth, as three independently-fingerprinted feeds the -`implementation-corpus` responsibility subscribes to: - -- `docs`: the planning documents in the run, each carrying its requested work - items (by lane). -- `repo`: the target repo snapshot — branch, sha, and the shared shape the - foundation owns. -- `config`: the run config — enabled lanes and the forbidden-operation policy. +`implementation-corpus` responsibility subscribes to. **Canonicalization spec**: each feed slice is fingerprinted on its own. A docs-only edit moves ONLY the `docs` facet; the `repo` and `config` facets stay byte-identical, so a re-POST that changed nothing does not move the fingerprint. This is the root of the dark-lane: surprise is feed-local from the very edge. -### Facets - -Named parts of this truth — each is a fingerprint unit and a subscription symbol. +**Facets** — the three feeds are the named parts of this truth. Each `####` +part below is a fingerprint unit and a subscription symbol. #### docs -The planning documents and their requested work items. Material: the doc ids and -their item lists; immaterial: transport request-ids and re-POST timestamps. +The planning documents in the run, each carrying its requested work items (by +lane). Material: the doc ids and their item lists; immaterial: transport +request-ids and re-POST timestamps. #### repo -The target repo snapshot. Material: branch, sha, and the shared shape; immaterial: -the scan timestamp. +The target repo snapshot — branch, sha, and the shared shape the foundation +owns. Material: branch, sha, and the shared shape; immaterial: the scan +timestamp. #### config -The run config. Material: enabled lanes and forbidden paths. +The run config — enabled lanes and the forbidden-operation policy. Material: +enabled lanes and forbidden paths. diff --git a/skills/open-prose/examples/inbox-triage/README.md b/skills/open-prose/examples/inbox-triage/README.md index 3f714573..9db9f8ac 100644 --- a/skills/open-prose/examples/inbox-triage/README.md +++ b/skills/open-prose/examples/inbox-triage/README.md @@ -53,8 +53,10 @@ email take the digest down. ``` 16 nodes / 27 edges. `gateway.inbox-stream` is the single entry point; the graph -is acyclic. `src/` ships the 4 authored contracts; the 16-node / 27-edge topology -is what a harness's expansion produces from them. +is acyclic. `src/` ships the 4 authored contracts and mounts 4 nodes on its own; +the 16-node / 27-edge topology is what the reference harness's per-entity +expansion produces from them (see the Harnesses section of the repository +README). ## Conformance expectations diff --git a/skills/open-prose/examples/monorepo-ci/README.md b/skills/open-prose/examples/monorepo-ci/README.md index df0d7982..4c379bc1 100644 --- a/skills/open-prose/examples/monorepo-ci/README.md +++ b/skills/open-prose/examples/monorepo-ci/README.md @@ -10,8 +10,9 @@ gate goes BLOCKED while the rest of the graph stays cached. This is the **largest** example in the library (22 nodes / 48 edges) and the one that teaches **memoization + hub fan-out blast radius**: a single `pkg-core` hub edit fans out to its dependents, while a leaf edit lights only one lane. -`src/` ships the 4 authored contracts; the 22-node / 48-edge topology is what a -harness's expansion produces from them. +`src/` ships the 4 authored contracts and mounts 4 nodes on its own; the +22-node / 48-edge topology is what the reference harness's per-entity expansion +produces from them (see the Harnesses section of the repository README). ## The DAG diff --git a/skills/open-prose/examples/monorepo-ci/src/merge-gate.prose.md b/skills/open-prose/examples/monorepo-ci/src/merge-gate.prose.md index 6a4ceac2..91a9afdf 100644 --- a/skills/open-prose/examples/monorepo-ci/src/merge-gate.prose.md +++ b/skills/open-prose/examples/monorepo-ci/src/merge-gate.prose.md @@ -2,7 +2,6 @@ name: merge-gate kind: responsibility version: 0.15.0 -id: gate.merge --- # Merge gate — the terminal verdict @@ -26,14 +25,20 @@ A gate world-model: `{ tests, review, typecheck, merge }` where `merge` is `GREEN` iff every recorded test status is `GREEN` and the review verdict is `approved`; otherwise `BLOCKED`. -### Continuity: input-driven +Postconditions, self-policed by the render before it signs: + +- postcondition: on the failing-`pkg-api`-test tick the gate renders + `merge: BLOCKED`. +- postcondition: on the cold boot and after the fix lands the gate renders + `merge: GREEN`. +- postcondition: a self-tick with no moved input is a `skipped` receipt that + lights no lane. + +### Continuity + +- input-driven +- self-driven Woken by an `input` wake when any fan-in producer moves, and by a `self` wake on a bare re-tick. A `self` tick in a quiet world finds no moved input and writes a `skipped` receipt — the audit floor: no work, no cost. - -### Postconditions - -- On the failing-`pkg-api`-test tick the gate renders `merge: BLOCKED`. -- On the cold boot and after the fix lands the gate renders `merge: GREEN`. -- A self-tick with no moved input is a `skipped` receipt that lights no lane. diff --git a/skills/open-prose/examples/monorepo-ci/src/package-build.prose.md b/skills/open-prose/examples/monorepo-ci/src/package-build.prose.md index 1013056a..f3077247 100644 --- a/skills/open-prose/examples/monorepo-ci/src/package-build.prose.md +++ b/skills/open-prose/examples/monorepo-ci/src/package-build.prose.md @@ -2,7 +2,6 @@ name: package-build kind: responsibility version: 0.15.0 -id: build.pkg-core --- # Package build — compile one package @@ -42,16 +41,18 @@ package's job. The merge gate reads this recorded status rather than the test node's stale published truth, so a tick whose test render fails is still seen by the gate as a non-passing job. -### Continuity: input-driven +Postconditions, self-policed by the render before it signs: + +- postcondition: a single-package leaf diff rebuilds ONLY that package + (`build.pkg-ui` alone); the other five builds stay skipped. +- postcondition: a hub (`pkg-core`) diff rebuilds core plus its three dependents + (`build.pkg-ui`, `build.pkg-api`, `build.pkg-auth`) and no more — `pkg-utils` + and `pkg-billing` stay dark. + +### Continuity + +- input-driven Woken only by an `input` wake from a producer whose facet it subscribes to. Fresh token cost scales with the lines of source this build had to recompile; nothing changed means a `skipped` receipt at zero fresh. - -### Postconditions - -- A single-package leaf diff rebuilds ONLY that package (`build.pkg-ui` alone); - the other five builds stay skipped. -- A hub (`pkg-core`) diff rebuilds core plus its three dependents - (`build.pkg-ui`, `build.pkg-api`, `build.pkg-auth`) and no more — `pkg-utils` - and `pkg-billing` stay dark. diff --git a/skills/open-prose/examples/monorepo-ci/src/package-test.prose.md b/skills/open-prose/examples/monorepo-ci/src/package-test.prose.md index 6e8b35f6..269c7087 100644 --- a/skills/open-prose/examples/monorepo-ci/src/package-test.prose.md +++ b/skills/open-prose/examples/monorepo-ci/src/package-test.prose.md @@ -2,7 +2,6 @@ name: package-test kind: responsibility version: 0.15.0 -id: test.pkg-api --- # Package test — run one package's suite @@ -28,15 +27,17 @@ subscribe to any other package's build. A test world-model: `{ pkg, rev, cases, passed }`. Fresh cost scales with the cases re-run (proportional to the changed lines the build recompiled). -### Continuity: input-driven +Postconditions, self-policed by the render before it signs: + +- postcondition: a passing run publishes `{ passed: true }` and lights its lane. +- postcondition: a broken run produces a `failed` receipt (fresh 0), publishes + nothing, and the merge gate sees a non-passing job and goes BLOCKED. + +### Continuity + +- input-driven Woken only by an `input` wake from its build. A broken suite throws instead of publishing, so the test's own truth goes stale while the build's recorded `testStatus` is `RED` — which is exactly what drives the merge gate to BLOCKED on that tick. - -### Postconditions - -- A passing run publishes `{ passed: true }` and lights its lane. -- A broken run produces a `failed` receipt (fresh 0), publishes nothing, and the - merge gate sees a non-passing job and goes BLOCKED. diff --git a/skills/open-prose/examples/monorepo-ci/src/workspace.prose.md b/skills/open-prose/examples/monorepo-ci/src/workspace.prose.md index ebe42472..f5e3a1e9 100644 --- a/skills/open-prose/examples/monorepo-ci/src/workspace.prose.md +++ b/skills/open-prose/examples/monorepo-ci/src/workspace.prose.md @@ -2,7 +2,6 @@ name: workspace kind: gateway version: 0.15.0 -id: gateway.workspace --- # Workspace — the monorepo CI gateway @@ -14,7 +13,9 @@ moves exactly one facet token; the other five tokens stay byte-identical, so the five sibling build/test/lint lanes never wake. That per-package split is the dark-lane boundary — it is what makes hub fan-out blast radius observable. -### Continuity: external-driven +### Continuity + +- external-driven This is the entry point: the working tree pushes new commits in from outside the graph. The gateway is woken by an `external` wake and never by an upstream node. @@ -26,6 +27,13 @@ canonicalizer projects each package slice into its own facet. A `workspace` world-model: a `packages` map keyed by package name, each slice carrying `{ name, rev, diffLines, head, testBroken }`. +Postconditions, self-policed by the render before it signs: + +- postcondition: exactly one package facet moves per single-package diff; the + sibling facets are byte-identical to the prior frame. +- postcondition: a byte-identical re-scan moves no facet at all (the whole graph + memo-skips). + #### pkg-core The hub facet. `build.pkg-core` subscribes to it; `pkg-core`'s compiled output @@ -54,9 +62,3 @@ A leaf-package facet and a hub dependent (rebuilds on a `pkg-core` change). #### pkg-billing An independent leaf facet — no hub dependency. Stays dark even on a hub diff. - -### Postconditions - -- Exactly one package facet moves per single-package diff; the sibling facets - are byte-identical to the prior frame. -- A byte-identical re-scan moves no facet at all (the whole graph memo-skips). diff --git a/skills/open-prose/examples/oblique-weave/src/product-signal-inbox.prose.md b/skills/open-prose/examples/oblique-weave/src/product-signal-inbox.prose.md index 7c8ce715..ffbb0a73 100644 --- a/skills/open-prose/examples/oblique-weave/src/product-signal-inbox.prose.md +++ b/skills/open-prose/examples/oblique-weave/src/product-signal-inbox.prose.md @@ -13,7 +13,9 @@ version: 0.15.0 > and its `### Continuity` is **external-driven**, which is how Forme finds it as a > DAG entry point. -### Continuity: external-driven +### Continuity + +- external-driven A webhook, a manual paste, or a scheduled poll translates into a *receipt* at the edge of the system — one wake event type, an external source. The gateway turns diff --git a/skills/open-prose/examples/oblique-weave/src/weave-config.prose.md b/skills/open-prose/examples/oblique-weave/src/weave-config.prose.md index b390cd24..81778107 100644 --- a/skills/open-prose/examples/oblique-weave/src/weave-config.prose.md +++ b/skills/open-prose/examples/oblique-weave/src/weave-config.prose.md @@ -14,7 +14,9 @@ version: 0.15.0 > same-epoch cycle back from the auditor). Its `### Continuity` is > **external-driven**. -### Continuity: external-driven +### Continuity + +- external-driven An operator edit, or a controller that lifts the Novelty Auditor's `recommended_viewport_shift` into an applied config, translates into a *receipt* diff --git a/skills/open-prose/examples/press-desk/README.md b/skills/open-prose/examples/press-desk/README.md index 0e7432b4..9898215d 100644 --- a/skills/open-prose/examples/press-desk/README.md +++ b/skills/open-prose/examples/press-desk/README.md @@ -59,8 +59,9 @@ action a human must own, and no sender PII ever escapes into a public projection acyclic. (The `speaking` register facet is a *zero-consumer-until-it-moves* lane: no speaking inquiry is delivered in the scripted episode, so it never wakes — the same discipline that keeps the dark lanes still.) `src/` ships the 4 authored -contracts; the 8-node / 14-edge topology is what a harness's expansion produces -from them. +contracts and mounts 4 nodes on its own; the 8-node / 14-edge topology is what +the reference harness's per-entity expansion produces from them (see the +Harnesses section of the repository README). ## Conformance expectations diff --git a/skills/open-prose/examples/research-inbox-triage/src/research-inbox-responsibility.prose.md b/skills/open-prose/examples/research-inbox-triage/src/research-inbox-responsibility.prose.md index 6df8e613..92cae137 100644 --- a/skills/open-prose/examples/research-inbox-triage/src/research-inbox-responsibility.prose.md +++ b/skills/open-prose/examples/research-inbox-triage/src/research-inbox-responsibility.prose.md @@ -15,10 +15,12 @@ next actions for the team's active questions. ### Requires - `inbox-items`: a current view of new papers, links, notes, or questions - awaiting triage + awaiting triage — the `inbox-items` truth of `inbox-gateway` - `active-questions`: research questions, initiatives, or watch areas that - should influence priority -- `available-owners`: people or roles who can accept follow-up work + should influence priority — the `#### active-questions` facet of + `research-registry` +- `available-owners`: people or roles who can accept follow-up work — the + `#### available-owners` facet of `research-registry` ### Maintains @@ -54,7 +56,8 @@ repeated re-triage. ### Continuity - input-driven: new inbox items wake triage; they should be triaged before they - are more than one business day old + are more than one business day old. A registry change (a question re-scoped, + an owner added) re-ranks and re-assigns without waiting for new items. - self-driven: re-surface stale high-priority items when no owner has accepted the follow-up diff --git a/skills/open-prose/examples/research-inbox-triage/src/research-registry.prose.md b/skills/open-prose/examples/research-inbox-triage/src/research-registry.prose.md new file mode 100644 index 00000000..1445fbb7 --- /dev/null +++ b/skills/open-prose/examples/research-inbox-triage/src/research-registry.prose.md @@ -0,0 +1,56 @@ +--- +name: research-registry +kind: gateway +version: 0.18.0 +--- + +# Research Registry + +> The human-maintained side of triage: the team's active research questions and +> the roster of people who can accept follow-up. Both are edited outside the +> graph, so they enter it through a gateway — no `### Requires`, the latest +> registry as `### Maintains`, and an **external-driven** `### Continuity`. The +> two facets keep a roster change from re-ranking the inbox: priority reads +> `#### active-questions`, assignment reads `#### available-owners`. + +### Continuity + +- external-driven + +### Receives + +- Edits to the team's research-questions document: a question opened, closed, + or re-scoped +- Edits to the owner roster: a person or role added, removed, or marked + unavailable +- Local event: registry file saved + +### Maintains + +The current registry as structured truth. Edit timestamps and editor identities +are immaterial; a save that changes no question or owner moves no facet, so +triage does not re-run for a cosmetic edit. Each `####` part below is a facet: +its name is the fingerprint unit, the subscription symbol +`research-inbox-responsibility` names in `### Requires`, and the +`published//…` subtree. + +#### active-questions + +The research questions, initiatives, and watch areas that should influence +priority. Material: the question set (unordered) and each question's id, title, +scope, and status; wording edits that keep the scope are immaterial. + +#### available-owners + +The people or roles who can accept follow-up work. Material: the owner set +(unordered) and each owner's id, role, and availability. + +### Emits + +- research-inbox-responsibility + +### Payload + +Pass the full current registry — every active question and every available +owner — as the incoming truth; the registry is small enough that a +whole-registry delivery is the normal shape. diff --git a/skills/open-prose/examples/research-tree/README.md b/skills/open-prose/examples/research-tree/README.md index 09b3f1af..5cbfdb38 100644 --- a/skills/open-prose/examples/research-tree/README.md +++ b/skills/open-prose/examples/research-tree/README.md @@ -66,8 +66,10 @@ and watch only its ancestor path re-synthesize. ## The state-dir a run produces -`src/` ships the 4 authored contracts; the 13-node / 20-edge topology is what a -harness's expansion produces from them. Below is one harness's replay layout; +`src/` ships the 4 authored contracts and mounts 4 nodes on its own; the +13-node / 20-edge topology is what the reference harness's per-entity expansion +produces from them (see the Harnesses section of the repository README). Below +is one harness's replay layout; the skill's native layout is `state/world-model/{node}/` with a per-node `receipts.jsonl` (`state/filesystem.md`). diff --git a/skills/open-prose/examples/session-to-prose/src/session-to-prose.prose.md b/skills/open-prose/examples/session-to-prose/src/session-to-prose.prose.md index 24e2471c..75a79cf7 100644 --- a/skills/open-prose/examples/session-to-prose/src/session-to-prose.prose.md +++ b/skills/open-prose/examples/session-to-prose/src/session-to-prose.prose.md @@ -12,7 +12,7 @@ session id that can be resolved from local session roots. This is a sequential generation pipeline, so it flattens into a single called `function` whose `### Execution` drives the steps in order; each former service -is now an inline `function` this render `call`s (`plan.md` §3, §7). The 15 steps +is now an inline `function` this render `call`s. The 15 steps appear below as `## name` sections with `### Parameters` → `### Returns`. ### Parameters diff --git a/skills/open-prose/examples/support-inbox-router/README.md b/skills/open-prose/examples/support-inbox-router/README.md index 47f0eb9f..ce91e8c3 100644 --- a/skills/open-prose/examples/support-inbox-router/README.md +++ b/skills/open-prose/examples/support-inbox-router/README.md @@ -62,8 +62,9 @@ to — so each downstream wakes only when ITS channel moves. 11 nodes / 16 edges. `gateway.support-inbox` is the single entry point; the graph is acyclic. `#### billing` is a fingerprinted facet with zero subscribers. `src/` -ships the 6 authored contracts; the 11-node / 16-edge topology is what a -harness's expansion produces from them. +ships the 6 authored contracts and mounts 6 nodes on its own; the 11-node / +16-edge topology is what the reference harness's per-entity expansion produces +from them (see the Harnesses section of the repository README). ## Conformance expectations diff --git a/skills/open-prose/examples/surprise-cost/src/signals.prose.md b/skills/open-prose/examples/surprise-cost/src/signals.prose.md index 0b6523f6..0f31d026 100644 --- a/skills/open-prose/examples/surprise-cost/src/signals.prose.md +++ b/skills/open-prose/examples/surprise-cost/src/signals.prose.md @@ -12,7 +12,9 @@ version: 0.15.0 > `### Continuity` is **external-driven**, which is how Forme finds it as a DAG > entry point. -### Continuity: external-driven +### Continuity + +- external-driven A webhook, a scheduled poll, or a manual kick translates into a *receipt* at the edge of the system — one wake event type, an external source. The gateway turns diff --git a/skills/open-prose/examples/vendor-renewal-watch/src/prepare-renewal-brief.prose.md b/skills/open-prose/examples/vendor-renewal-watch/src/prepare-renewal-brief.prose.md index 4d76b07a..bf358102 100644 --- a/skills/open-prose/examples/vendor-renewal-watch/src/prepare-renewal-brief.prose.md +++ b/skills/open-prose/examples/vendor-renewal-watch/src/prepare-renewal-brief.prose.md @@ -10,7 +10,7 @@ id: 067NC4KG11RN54TMANB5EP2SBA > A downstream mounted node that subscribes to a single **facet** of the > assessor's truth — `recommendation` — so it wakes when a vendor's posture moves > and **not** when only the decision-history ledger churns. This is the facet -> selector (`world-model.md` §3): atomic-only would wake the brief writer on every +> selector: atomic-only would wake the brief writer on every > history append; a facet subscription wakes it exactly when the decision moved. ### Goal diff --git a/skills/open-prose/forme.md b/skills/open-prose/forme.md index 4d7bcf39..63d7c117 100644 --- a/skills/open-prose/forme.md +++ b/skills/open-prose/forme.md @@ -172,6 +172,13 @@ For each matched need, draw a `TopologyEdge`: `subscriber.Requires.` → `producer.Maintains.` (the matched facet, or `@atomic` when the producer declares none). +A facet-need written with a placeholder (`session:`, `eligible:`) +matches any member of the family it names — a producer's `#### session:` +part, or one of a literal set that shares the placeholder's shape; the harness +binds the member at mount time. Forme draws the edge to the family and never +enumerates entities it cannot know at compile time. A need for *every* member +of a family is deliberate fan-in, handled in Step 3. + ### Step 3: Honor deliberate fan-in (the diamond rule) When a contract deliberately asks for *many* producers of the same kind of truth diff --git a/skills/open-prose/guidance/authoring.md b/skills/open-prose/guidance/authoring.md index 910dff4d..d1e3145d 100644 --- a/skills/open-prose/guidance/authoring.md +++ b/skills/open-prose/guidance/authoring.md @@ -16,10 +16,11 @@ Use this file when writing or reviewing OpenProse author-facing artifacts: Every authored file is **one render** — a contract plus the bounded session that runs it. The `kind` field is sugar over that single render atom: each kind is the -same render with different or missing sections (`plan.md` §1). There is **no +same render with different or missing sections (the authored kinds in +`contract-markdown.md`). There is **no `kind: system`** and **no `kind: service`**: composition is imperative `call` *inside* a render or a cross-node *subscription* across responsibilities, never a -third internally-autowired graph kind (`plan.md` §3). +third internally-autowired graph kind. ## Core Principles @@ -34,7 +35,7 @@ third internally-autowired graph kind (`plan.md` §3). obvious to a caller and to Forme. - Use `### Execution` only when order, loops, retries, gates, or branches are part of the requirement. It is the intra-node render body, and none of it is a - node (`plan.md` §7). + node (the execution-section rules in `contract-markdown.md`). - Treat the render's private `workspace/` as scratch that is never fingerprinted, and the canonical published world-model as the subscribable truth. Downstream work reads the published world-model, never upstream scratch. @@ -47,14 +48,14 @@ A `kind: responsibility` file defines a mounted DAG node: a standing truth kept current over time. It declares **both halves of its interface** — `### Requires` (its subscription contracts) and `### Maintains` (the shape of the truth it keeps) — plus its wake-source in `### Continuity`. It is a node because it is -mounted as a subscribable producer, **not** because it holds state (`plan.md` -§2). +mounted as a subscribable producer, **not** because it holds state. - Put facet-level needs in `### Requires`. Each entry names a facet contract that Forme matches semantically to some producer's `### Maintains` facet (`Requires. ↔ Maintains.`). `### Requires` is the *need*; the resolved producer is Forme's choice. -- Make `### Maintains` do its four jobs (`world-model.md` §2): a **type** (the +- Make `### Maintains` do its four jobs (spelled out under `## Maintains` in + `contract-markdown.md`): a **type** (the fields, including freshness fields like `valid_until` / `last_corroborated`); a **canonicalization spec** (what equality means — which fields are material and which are volatile-but-immaterial, such as `fetched_at` timestamps and request @@ -69,11 +70,11 @@ mounted as a subscribable producer, **not** because it holds state (`plan.md` - Honor the structured-backing rule: anything subscribed must have a structured, canonicalizable backing. Fingerprint the structured truth and render prose *from* it; free-form rendered prose is a derived projection excluded from the - fingerprint (`world-model.md` §3). + fingerprint (the structured-backing rule). - Declare freshness *state* (`valid_until`, `last_corroborated`, `confidence`) in `### Maintains` and freshness *policy* (the recheck cadence) in `### Continuity`. A lapsing `valid_until` flips a fact's status, moves that - facet's fingerprint, and propagates as ordinary surprise (`world-model.md` §6). + facet's fingerprint, and propagates as ordinary surprise. - State postconditions as conditions on the output (the folded-in `### Criteria`), not a separate judge beat. Deterministically-expressible postconditions are verified by the harness on commit; irreducibly-semantic ones are self-attested @@ -276,10 +277,10 @@ subject: summarizer ## World-Model and Freshness Authoring A responsibility's persisted world-model **is** its memory: one canonical truth -per node subsumes the old `### Memory` reads/writes ledger (`world-model.md` -§9.4). There is no separate `### Memory` section. A `function` is stateless and -has no world-model; a former helper that was genuinely stateful is really a -responsibility, and its persisted state is its world-model. +per node subsumes the old `### Memory` reads/writes ledger. There is no separate +`### Memory` section. A `function` is stateless and has no world-model; a former +helper that was genuinely stateful is really a responsibility, and its persisted +state is its world-model. - Declare the durable shape — decision history, watermarks, cursors, and per-entity truth — in `### Maintains`, with facets so a downstream wakes only @@ -289,11 +290,11 @@ responsibility, and its persisted state is its world-model. the run. For recurring workflows, keep cursors, high-water marks, and run ids as material fields of the maintained truth. - Treat the published world-model as the single canonical truth. SQL, vector, and - dashboard views over it are derived projections, never the truth - (`world-model.md` §1). + dashboard views over it are derived projections, never the truth (the + canonical-truth invariant in `state/README.md`). - The render writes the world-model and signs a receipt with its fingerprints by applying the compiled canonicalizer locally; this works standalone, with no - harness present (`architecture.md` §3.2). + harness present (the render harness contract in `primitives/session.md`). ## Repository Authoring diff --git a/skills/open-prose/prose.md b/skills/open-prose/prose.md index 445855ae..c20febfe 100644 --- a/skills/open-prose/prose.md +++ b/skills/open-prose/prose.md @@ -321,12 +321,13 @@ and `PROSE_PREV_RECEIPT` (the prior receipt, by reference). Load the referenced compiled intent from the active OpenProse root before execution, then continue as a normal bounded render. -There is no judge beat, no responsibility status enum, no pressure record. A node -runs because the **reconciler** compared fingerprints and found that either the -node's own `contract_fingerprint` or one of its subscribed `input_fingerprints` -moved (`world-model.md` §3). The wake decision is deterministic and total — never -an LLM judgment — and every wake, from any of the three sources, arrives as a -receipt the render reads by reference. +There is no judge beat, no responsibility status enum, no pressure record. A +node runs because the **reconciler** compared fingerprints and found that either +the node's own `contract_fingerprint` or one of its subscribed +`input_fingerprints` moved (the fingerprint rules in `concepts/reconciler.md`). +The wake decision is deterministic and total — never an LLM judgment — and every +wake, from any of the three sources, arrives as a receipt the render reads by +reference. --- @@ -849,9 +850,10 @@ through the richer render-harness seam below — not a dumb copy. ## The Render Harness Seam A **responsibility node** maintains a canonical world-model — its standing, -typed, subscribable truth (`world-model.md` §1). When the reconciler wakes a node -(because its `contract_fingerprint` or an `input_fingerprint` moved), the VM -runs a **render** under this harness contract: +typed, subscribable truth (the canonical-truth invariant in `state/README.md`). +When the reconciler wakes a node (because its `contract_fingerprint` or an +`input_fingerprint` moved), the VM runs a **render** under this harness +contract: 1. **Locate the prior world-model by reference.** The render is *not* handed the world-model stuffed into context. The harness tells it *where* the prior truth diff --git a/skills/open-prose/state/README.md b/skills/open-prose/state/README.md index 538bed87..e02095b3 100644 --- a/skills/open-prose/state/README.md +++ b/skills/open-prose/state/README.md @@ -36,7 +36,7 @@ repositories use the repository root, attached repositories use ## The truth is canonical; everything else is a projection -The load-bearing invariant for every backend (`world-model.md` §1): the +The load-bearing invariant for every backend: the **canonical world-model is a single content-addressable artifact** — by default a directory of files — that is deterministically serialized and fingerprinted on commit. SQLite tables, PostgreSQL rows, vector indices, and dashboards are diff --git a/skills/open-prose/state/filesystem.md b/skills/open-prose/state/filesystem.md index 82b2f056..555fea56 100644 --- a/skills/open-prose/state/filesystem.md +++ b/skills/open-prose/state/filesystem.md @@ -34,7 +34,8 @@ File-based state persists all execution artifacts to disk. This enables: **Key principle:** Files are inspectable artifacts. The directory structure IS the execution state. -**The load-bearing distinction** (`world-model.md` §1): a node's **published** +**The load-bearing distinction** (the invariant `state/README.md` states for every +backend): a node's **published** world-model is the canonical, deterministically-serialized, fingerprinted artifact — the truth downstreams subscribe to. The render's **workspace** is private scratch — intermediate reasoning, working notes — and is **never @@ -214,15 +215,16 @@ world-model/ Note: `report.md` here is the *structured* truth that backs the fingerprint. Free-form rendered prose is a **derived projection excluded from the -fingerprint** (`world-model.md` §3, the structured-backing rule) — otherwise an -LLM re-rendering the same paragraph hashes differently every time and falsely -re-triggers downstreams. Fingerprint the structured truth; render prose *from* it. +fingerprint** (the structured-backing rule in `contract-markdown.md`) — +otherwise an LLM re-rendering the same paragraph hashes differently every time +and falsely re-triggers downstreams. Fingerprint the structured truth; render +prose *from* it. #### Faceted layout — one subtree per facet When a node's `### Maintains` declares facets by **naming the parts** (a `####` -sub-heading inside `### Maintains` *is* a facet — the named-parts rule, -`delta.md` Part G; `world-model.md` §3, "Declaring facets"), the published +sub-heading inside `### Maintains` *is* a facet — the named-parts rule in +`contract-markdown.md`), the published artifact lays each facet out as its own subtree under the node directory: `published//…`. The directory structure *is* the subscription surface — the same name is the facet's fingerprint unit, its @@ -245,8 +247,8 @@ world-model/ └── .version # ContentAddress of this committed version ``` -**The fingerprinting rule with facets** (`world-model.md` §3; the -canonical-serialization pass below). The single authority for facet tokens is the +**The fingerprinting rule with facets** (see the canonical-serialization pass +below). The single authority for facet tokens is the **compiled canonicalizer** that travels with the contract: it reduces the node's *structured* material truth (the `### Maintains` canonicalization spec, frozen at compile, addressing material fields by their dotted structured paths) @@ -264,7 +266,7 @@ canonicalizer computes over the facet's material structured content. that `### Requires` `funding` and resolves to `#### funding` subscribes to that facet's token: a move in the `hiring` facet advances the `hiring` token and the `@atomic` token but **not** the `funding` token, so the funding-only subscriber - does not wake (the selector boundary, `world-model.md` §3). + does not wake (the selector boundary). - Shared un-facetted fields (here `competitors.md`'s `name` / `last_corroborated`) belong to no facet's material content, so they move only the `@atomic` token. @@ -288,7 +290,7 @@ canonicalize (drop immaterial) → fingerprint → sign receipt*: ordering (sorted by normalized path), path/encoding normalization, and sorted-key JSON for any structured records (reuses the receipt's canonical-JSON machinery). The same byte sequence must result regardless of write order or - filesystem enumeration order (`architecture.md` §5.2, §10). + filesystem enumeration order. 3. **Apply the compiled canonicalizer** for this node (`### Maintains` canonicalization spec, frozen at compile time): drop immaterial fields (`fetched_at`, request ids, cosmetic ordering), normalize sets/numbers/text to @@ -308,7 +310,7 @@ canonicalize (drop immaterial) → fingerprint → sign receipt*: The reconciler's wake decision is this fingerprint comparison — deterministic, total, no LLM. **Only a `rendered` receipt with a moved fingerprint propagates to -downstreams** (`world-model.md` §8). +downstreams** (the reconcile loop in `concepts/reconciler.md`). --- @@ -522,7 +524,7 @@ To resume an interrupted run: 1. Read the `receipts/` ledger — the append-only chain is the source of truth; find the last committed receipt per node 2. Read the compiled intent — get the topology and propagation edges 3. Scan `world-model/` — confirm published canonical artifacts (and their `.version`) -4. Re-derive reconciler dirty/coalesce state from unconsumed upstream receipts and continue (`architecture.md` §8) +4. Re-derive reconciler dirty/coalesce state from unconsumed upstream receipts and continue --- @@ -576,7 +578,8 @@ If the render wrote `__error.md` instead: 1. **VM reads** `workspace/{node}/__error.md` 2. **VM emits a `failed` receipt** — a failed render commits nothing; the prior - world-model stands and no fingerprint moves (`architecture.md` §8: failure = no-commit) + world-model stands and no fingerprint moves (failure = no-commit; see + `concepts/reconciler.md`) 3. **VM appends** error marker to `vm.log.md` --- @@ -684,5 +687,5 @@ fingerprints — deterministic, total, no LLM. The render writes to workspace. T VM commits the canonical world-model through the canonical-serialization-before-fingerprint pass and signs a receipt — never a dumb copy. SQL/vector indices, when present, are **derived projections** of the -canonical truth (`world-model.md` §1). Everything is on disk, everything is +canonical truth. Everything is on disk, everything is inspectable, everything is auditable through the signed receipt chain. diff --git a/skills/open-prose/state/in-context.md b/skills/open-prose/state/in-context.md index 3f8fba95..1fbdcedb 100644 --- a/skills/open-prose/state/in-context.md +++ b/skills/open-prose/state/in-context.md @@ -23,7 +23,8 @@ In-context state uses text-prefixed markers to persist state within the conversa **Key principle:** Your conversation history IS the VM's working memory. -**The canonical world-model still holds even in-memory** (`world-model.md` §1): a +**The canonical world-model still holds even in-memory** (the invariant every +backend shares, see `state/README.md`): a node's published, fingerprinted truth and its append-only receipt chain are narrated as canonical state in the conversation, while the render's private working notes are *workspace* scratch — never fingerprinted, never subscribed to. diff --git a/skills/open-prose/state/postgres.md b/skills/open-prose/state/postgres.md index d914d540..2133d69e 100644 --- a/skills/open-prose/state/postgres.md +++ b/skills/open-prose/state/postgres.md @@ -64,7 +64,8 @@ PostgreSQL state provides: ### SQL/vector are derived projections, not the truth -The load-bearing invariant (`world-model.md` §1): **the canonical world-model is a +The load-bearing invariant (the one `state/README.md` states for every backend): +**the canonical world-model is a single content-addressable artifact, and PostgreSQL rows and vector indices are derived projections of it, never the truth.** Under this backend PostgreSQL holds two canonical things — the **append-only receipt ledger** and the @@ -946,7 +947,7 @@ PostgreSQL state management: 1. Uses a **shared PostgreSQL database** for all runs 2. Holds the **canonical** receipt ledger + content-addressed world-model versioning in tables -3. Exposes **SQL/JSONB + vector indices as derived projections** — never the canonical truth (`world-model.md` §1) +3. Exposes **SQL/JSONB + vector indices as derived projections** — never the canonical truth 4. Has **no policy/responsibility-status/pressure registry** — the wake decision is the reconciler comparing fingerprints 5. Provides **true concurrent writes** via row-level locking; **network access** for dashboards; **team collaboration** 6. Allows **flexible schema evolution** for projections with JSONB and custom tables diff --git a/skills/open-prose/state/sqlite.md b/skills/open-prose/state/sqlite.md index 6ca97d31..a9cde7a1 100644 --- a/skills/open-prose/state/sqlite.md +++ b/skills/open-prose/state/sqlite.md @@ -48,7 +48,8 @@ SQLite state provides: ### SQL is a derived projection, not the truth -The load-bearing invariant (`world-model.md` §1): **the canonical world-model is a +The load-bearing invariant (the one `state/README.md` states for every backend): +**the canonical world-model is a single content-addressable artifact, and SQL is a derived projection of it, never the truth.** SQLite under this backend holds two things that *are* canonical — the **append-only receipt ledger** and the **content-addressed world-model versioning** @@ -637,7 +638,7 @@ SQLite state management: 1. Uses a **single database file** per run 2. Holds the **canonical** receipt ledger + content-addressed world-model versioning in tables -3. Exposes **SQL query tables as derived projections** — never the canonical truth (`world-model.md` §1) +3. Exposes **SQL query tables as derived projections** — never the canonical truth 4. Has **no policy/responsibility-status/pressure registry** — the wake decision is the reconciler comparing fingerprints 5. Uses **append-only writes** for the ledger 6. Allows **flexible schema evolution** for projections as needed diff --git a/spec/01-Language.md b/spec/01-Language.md index 64594a99..ba9a0d1e 100644 --- a/spec/01-Language.md +++ b/spec/01-Language.md @@ -292,16 +292,14 @@ The current repository narrows the ground truth: reserved/inert. Current dependency resolution is explicit git-host based, with `std/` and `co/` shorthands. - The current repo does not specify a product/business platform surface like - Cloud billing, sprites, Constellation, or an investor narrative. The hosted - product, public/social surface, and go-to-market motion are sketched in - [ContinuousOutcomes.md](../ideation/ContinuousOutcomes.md) (stale - product ideation, not a live spec); the forward-looking subscription, - royalty, and dependency-graph economics are sketched in - [SubscriptionsHypothetical.md](../ideation/SubscriptionsHypothetical.md) - (hypothetical, not a live spec and not load-bearing); the brand and design - surface lives in `platform/apps/run/PRODUCT.md`. None of those documents are - live OpenProse language/runtime material; this file remains the language and - runtime spec only. + Cloud billing, sprites, Constellation, or an investor narrative. An earlier + ideation note explored the hosted product, public/social surface, and + go-to-market motion (stale product ideation, not a live spec); another + sketched forward-looking subscription, royalty, and dependency-graph + economics (hypothetical, not a live spec and not load-bearing); the brand and + design surface is a product concern kept outside this repository. None of + that is live OpenProse language/runtime material; this file remains the + language and runtime spec only. ### Prose Complete @@ -353,7 +351,7 @@ The root layout is: | `runs/` | Activation receipts for bounded VM runs | | `state/` | Durable cross-run state | | `state/agents/` | Durable agent memory | -| `state/world-model/{node}/` | Each responsibility's persisted canonical world-model, with its signed, append-only `receipts.jsonl` ledger — the language VM root layout (a harness may lay out its own state-dir differently, [02-Harness.md](./02-Harness.md)); no separate status/pressure store — the judge loop is retired | +| `state/world-model/{node}/` | Each responsibility's persisted canonical world-model, with its signed, append-only receipt ledger in a harness-chosen receipt ledger layout (the reference harness ships a flat `receipts.json`) — this is the language VM root layout, and a harness may lay out its own state-dir differently ([02-Harness.md](./02-Harness.md)); no separate status/pressure store — the judge loop is retired | | `deps/` | Installed git-native dependencies | | `prose.lock` | Dependency lockfile | | `.env` | Local runtime environment | @@ -401,12 +399,12 @@ Postconditions: input-driven; self-driven daily. ``` -A `kind: responsibility` file carries a required `id:` frontmatter field: a -tooling-generated, UUIDv7-compatible identifier (rendered as uppercase Crockford -base32) minted once by `prose` and preserved across `name:` and filename -renames. `name:` is the human-facing slug; `id:` is the durable identity used to -key world-model and receipt-ledger state under `state/world-model/{node}/`. -Authors do not hand-write `id:`; tooling manages it. +A `kind: responsibility` or `kind: gateway` file may carry an `id:` frontmatter +field: a tooling-minted, UUIDv7-compatible identifier (rendered as 26 characters +of uppercase Crockford base32) that gives the contract an identity surviving +`name:` and filename renames. Without one, the slug is the identity. World-model +and receipt-ledger state is keyed by the mounted node, which defaults to the +slug. Authors do not hand-write `id:`; tooling mints it. The five current authored kinds are: @@ -956,31 +954,33 @@ on; a harness tracks its own engineering detail. Part I's self-driven continuity — a lapsed `valid_until` mechanically moves a facet fingerprint via the self-tick — needs a default projector in the served -continuity loop. Today the reference harness's loop runs on a flat poll cadence -and arms no per-facet `valid_until` by default, so -`### Continuity: self-driven` runs at a fixed interval rather than the Ideal's -"wake exactly when the soonest `valid_until` lapses." The author already writes -the `valid_until`; the cadence tightens harness-side without a source change. -([02-Harness.md](./02-Harness.md) *Open specification items* §6.) +continuity loop. As of the reference harness 0.3.3 (see the repository README +under *Harnesses*), its loop runs on a flat poll cadence and arms no per-facet +`valid_until` by default, so `### Continuity: self-driven` runs at a fixed +interval rather than the Ideal's "wake exactly when the soonest `valid_until` +lapses." The author already writes the `valid_until`; the cadence tightens +harness-side without a source change. ([02-Harness.md](./02-Harness.md) *Open +specification items* §6.) ### 2. The deterministic commit gate, wired -`### Maintains` postconditions lower to deterministic validators today -(`compilePostconditions` runs on the compile path), but the gate that evaluates -them at commit, `gateCommit`, is built and **unwired** — the live commit rides the -render's own `### Maintains` self-attestation. The language promises "a render -that cannot satisfy its postconditions commits nothing"; making that -deterministic rather than self-attested is harness wiring, not a syntax change. +`### Maintains` postconditions lower to deterministic validators (in the +reference harness 0.3.3, `compilePostconditions` runs on the compile path), but +the gate that evaluates them at commit, `gateCommit`, is built and **unwired** +as of that release — the live commit rides the render's own `### Maintains` +self-attestation. The language promises "a render that cannot satisfy its +postconditions commits nothing"; making that deterministic rather than +self-attested is harness wiring, not a syntax change. ([02-Harness.md](./02-Harness.md) invariant 6.) ### 3. The failed-receipt reason (durable receipt shape) -The language promises a `failed` receipt "addressed to the author, naming exactly -what it needs." The shipped v0 receipt is too thin to carry that — no `as_of`, no -failure `reason`, no author-addressing field — so the naming is honored only -in-render and dropped at commit. Widening the durable receipt is harness work the -language's failed-receipt promise depends on. ([02-Harness.md](./02-Harness.md) -*Open specification items* §1.) +The language promises a `failed` receipt "addressed to the author, naming +exactly what it needs." The v0 receipt the reference harness 0.3.3 ships is too +thin to carry that — no `as_of`, no failure `reason`, no author-addressing field +— so the naming is honored only in-render and dropped at commit. Widening the +durable receipt is harness work the language's failed-receipt promise depends +on. ([02-Harness.md](./02-Harness.md) *Open specification items* §1.) ### 4. The actuation boundary, enforced diff --git a/spec/03-AuthoringPattern.md b/spec/03-AuthoringPattern.md index 9b802ddd..45627e42 100644 --- a/spec/03-AuthoringPattern.md +++ b/spec/03-AuthoringPattern.md @@ -400,9 +400,10 @@ What an ideal harness does with this: commits nothing, and leaves a `failed` receipt — the prior truth stands. Two of these are ideal harness behavior the contract is authored to, not yet -delivered: forecast-paced freshness arming (today `serve` polls a flat interval) -and the gateway's `### Schedule` cadence (today dropped by the compiler) are Part -II deferrals — the contract above is correct; the harness climbs to it. +delivered as of the reference harness 0.3.3 (see the repository README under +*Harnesses*): forecast-paced freshness arming (that harness's `serve` polls a +flat interval) and the gateway's `### Schedule` cadence (dropped by its compiler) +are Part II deferrals — the contract above is correct; the harness climbs to it. No `kind: system`, no `### Services`, no judge, no ledger service: the render maintains the world-model, the canonicalizer senses change, `gateCommit` gates @@ -485,11 +486,11 @@ kinds and sections Part I names, and only those. | `kind: pattern` / `kind: test` | Present; patterns expand at compile time, tests route to the in-session `prose test` semantic (a harness owns no test verb) | | Facets: `####` parts under `### Maintains` — name = fingerprint unit + subscription symbol; atomic default | Present and **facet-granular propagation is live in production** (the v2 named-parts model); a downstream subscribed to one facet does not wake when another moves | | `### Maintains` as the world-model schema doing four jobs (type, canonicalization spec, facets, postconditions) | Present; the postconditions live **inside `### Maintains`**, compiled to validators (there is no separate `### Criteria` section — it was removed) | -| `### Continuity` as the structural wake-source declaration (input / self / external) | Present; self-driven recheck and the gateway entry point are both wired (Phase 4) | +| `### Continuity` as the structural wake-source declaration (input / self / external) | Present; self-driven recheck and the gateway entry point are both wired | | `### Execution` ProseScript for variable-depth work inside one render | Present — an `if`-gated `call` to a `function` is the depth mechanism, not a judge tier | | Compile as SKILL-loaded sessions (Forme / canonicalizer / postcondition) → deterministic lowering → content-addressed IR cache | Present; a `.prose` set mounts without hand-authoring via a true semantic `Requires ↔ Maintains` match | | Run: dumb reconciler — memo-skip on unmoved `(contract_fp, input_fp)`, single-flight + coalescing, failure = no-commit, propagate only on a `rendered` moved fingerprint | Specified ([02-Harness.md](./02-Harness.md) *The canonical loop*); the reference harness realizes it, including restart survival (truth + ledger survive a fresh process) | -| `gateCommit`: deterministic postcondition validators + render self-attestation of `### Maintains`; receipt status in `{rendered, skipped, failed}` | Partial; no judge, no verdict, no status enum — but the commit rides the render's **self-attestation** today: in the reference harness the deterministic `gateCommit(...)` validators are built and tested yet have **zero live callers** ([02-Harness.md](./02-Harness.md) invariant 6) | +| `gateCommit`: deterministic postcondition validators + render self-attestation of `### Maintains`; receipt status in `{rendered, skipped, failed}` | Partial; no judge, no verdict, no status enum — but the commit rides the render's **self-attestation**: in the reference harness 0.3.3 the deterministic `gateCommit(...)` validators are built and tested yet have **zero live callers** ([02-Harness.md](./02-Harness.md) invariant 6) | | Content-addressed, chain-verifiable receipt ledger; cost = `tokens.fresh` vs `tokens.reused` + `surprise_cause` | Present (the reference harness chain-verifies and tamper-detects the ledger and renders "cost scales with surprise" from it) | | Composition: a downstream responsibility names an upstream facet in `### Requires`; Forme draws the subscription edge | Present; the reconciler reads the topology `edges` to resolve propagation | @@ -516,20 +517,21 @@ reality is honest, rule by rule. | 4. Bounded activations | Conformant | Idiomatic; the "loop until done" anti-pattern is author error, not a skill gap | | 5. Written for memoization / variable depth | Conformant | The reconciler's skip on `(contract_fp, input_fp)` is live (cost scales with surprise, including an immaterial-churn re-poll that still skips); facet selectors are live; depth is an `if`-gated `call` in `### Execution`. The author's leverage is real today | | 6. Composition via a subscribed upstream facet | Conformant (meaning-layer) | A downstream names the upstream facet in `### Requires` and Forme wires the edge; receipts are content-addressed and the chain is verifiable. The **cryptographic** signer is a null-state — v1 "signed" is meaning-layer chain-consistency, not a byte-hash — so cross-trust-domain *pinning to a signer set* is not yet enforceable | -| 7. Receipts as the audit / composition / exit unit | Conformant | The ledger is flat `/receipts.json`, content-addressed, chain-verifiable; `cost` (fresh/reused/surprise_cause) and `status` are first-class. No hand-rolled scratch log is needed | +| 7. Receipts as the audit / composition / exit unit | Conformant | The ledger is a harness-chosen receipt ledger layout (the reference harness ships a flat `receipts.json`), content-addressed, chain-verifiable; `cost` (fresh/reused/surprise_cause) and `status` are first-class. No hand-rolled scratch log is needed | | 8. Replayable and exitable | Conformant | Contract + world-model + ledger are plain and portable; a replay viewer replays a saved run with no running harness and zero key | ### Honest current limits for authors - **`### Continuity` self-driven recheck exists, but `serve`'s freshness clock arms nothing by default.** The bridge is real (a lapsed `valid_until` flips a - fact's status, moves the facet fingerprint, and wakes the node — a **zero-token** - fingerprint move, not a model re-render), and the SDK computes each node's - soonest `next_self_recheck`. But the shipped `serve` daemon's freshness reader - defaults to none and no node emits `valid_until` by default, so it sleeps a flat - `--poll-interval` (default 60s) and does the fixed-interval work the ideal - forbids. Forecast-paced / adaptive idle is the deferred next step. Declare - `valid_until` now; the cadence tightens later without a source change. + fact's status, moves the facet fingerprint, and wakes the node — a + **zero-token** fingerprint move, not a model re-render), and the SDK computes + each node's soonest `next_self_recheck`. But as of the reference harness + 0.3.3, its `serve` daemon's freshness reader defaults to none and no node + emits `valid_until` by default, so it sleeps a flat `--poll-interval` (default + 60s) and does the fixed-interval work the ideal forbids. Forecast-paced / + adaptive idle is the deferred next step. Declare `valid_until` now; the + cadence tightens later without a source change. - **The `### Invariants` actuation boundary is authored, not enforced.** The authored rate/scope/prohibited-action quarantine is never lowered into the render — neither compiled, attested, nor harness-checked — so it constrains the @@ -537,12 +539,14 @@ reality is honest, rule by rule. the turn cap. There is also no world-mutation actuation sink yet (render tools are fs/shell over a private workspace; connectors are read-only ingress). - **A gateway's `### Schedule` cadence is not yet honored.** Gateways poll - external sources, but a per-gateway `### Schedule` (e.g. "every 6h") is dropped - by the compiler today; cadence is the serve loop's flat poll interval. Declare - the intended schedule now; it binds once the compiler carries it. + external sources, but a per-gateway `### Schedule` (e.g. "every 6h") is + dropped by the compiler as of the reference harness 0.3.3; cadence is that + harness's flat serve poll interval. Declare the intended schedule now; it + binds once the compiler carries it. - **The deterministic commit gate is unwired.** `compilePostconditions(...)` - runs on the compile path, but the gate that evaluates it, `gateCommit(...)`, has - zero live callers; the commit rides the render's `### Maintains` self-attestation + runs on the compile path, but as of the reference harness 0.3.3 the gate that + evaluates it, `gateCommit(...)`, has zero live callers; the commit rides the + render's `### Maintains` self-attestation ([02-Harness.md](./02-Harness.md) invariant 6). - **Serve ingress is local cron-poll + HTTP only.** Gateway poll connectors and an HTTP trigger surface ship; **queues, file watches, and provider @@ -626,13 +630,14 @@ grammar; there is no `.prose` parser to build. The author states satisfaction as postconditions inside `### Maintains`, and the compile phase lowers them to deterministic validators (`compilePostconditions` -runs on the compile path). But the gate that would evaluate them at commit, -`gateCommit(...)`, has zero live callers — the commit rides the render's own -`### Maintains` self-attestation. So the author's postconditions are *compiled* -but not yet the *enforced* commit gate. Threading the compiled validators onto -the live commit step ([02-Harness.md](./02-Harness.md) invariant 6) makes Rule 3's "no attestation, no -commit" a deterministic guarantee rather than a render self-report. The author -writes postconditions to it now; the enforcement tightens harness-side. +runs on the compile path). But as of the reference harness 0.3.3, the gate that +would evaluate them at commit, `gateCommit(...)`, has zero live callers — the +commit rides the render's own `### Maintains` self-attestation. So the author's +postconditions are *compiled* but not yet the *enforced* commit gate. Threading +the compiled validators onto the live commit step +([02-Harness.md](./02-Harness.md) invariant 6) makes Rule 3's "no attestation, +no commit" a deterministic guarantee rather than a render self-report. The +author writes postconditions to it now; the enforcement tightens harness-side. ### 5. Lower and enforce the `### Invariants` actuation boundary @@ -648,13 +653,14 @@ the harness lowers it. ### 6. Forecast-paced continuity cadence The self-driven recheck path is wired (freshness-lapse → synthetic self-receipt, -a zero-token fingerprint move), but the reference harness's serve loop polls it -on a flat interval. The deferred work is arming each node's soonest +a zero-token fingerprint move), but the serve loop of the reference harness 0.3.3 +polls it on a flat interval. The deferred work is arming each node's soonest `next_self_recheck` off its freshness so an idle harness sleeps to the next real expiry instead of waking every interval — the *forecast-paced quiescence* Part I implies. The same step is owed for a gateway's declared `### Schedule`, which the -compiler drops today (so the gateway runs on the flat serve poll, not its -authored cadence); honoring it as a freshness-paced cadence rides with this work. +compiler drops as of the reference harness 0.3.3 (so the gateway runs on the flat +serve poll, not its authored cadence); honoring it as a freshness-paced cadence +rides with this work. The author already writes the `valid_until` (and the `### Schedule`) that feeds it; the cadence tightens harness-side, without a source change. diff --git a/tests/open-prose/compiler/README.md b/tests/open-prose/compiler/README.md index 33a78a5a..7ceb27d8 100644 --- a/tests/open-prose/compiler/README.md +++ b/tests/open-prose/compiler/README.md @@ -80,7 +80,7 @@ Expected shape: - lowers each `####` part into a facet (facet name = heading text, paths = the part's material fields), default-material within the part, and binds the shared un-facetted `name` / `last_corroborated` to the atomic facet only (the - named-parts rule, `architecture.md` §3.2) + named-parts rule) - emits the producer canonicalizer with `facets: ["@atomic", "funding", "hiring", "product-launches"]` - lowers the atomic-only subscriber `### Maintains` (no `####` parts) to diff --git a/tests/open-prose/compiler/compiler-ir.test.ts b/tests/open-prose/compiler/compiler-ir.test.ts index 25d37646..dcde941a 100644 --- a/tests/open-prose/compiler/compiler-ir.test.ts +++ b/tests/open-prose/compiler/compiler-ir.test.ts @@ -2,8 +2,7 @@ // // Two halves: // 1) Doc conformance — asserts skills/open-prose/compiler/ir-v0.md embodies the -// Intelligent React end-state (delta.md §B6 "REWRITE … the compile-phase -// seam"; §A5; architecture.md §2/§3/§6.3; world-model.md §3/§4/§5). The IR +// Intelligent React end-state (the compile-phase seam). The IR // carries topology + canonicalizers + postconditions + contract fingerprints // and deletes the judge-era manifest (activations/criteria/formeManifests). // 2) Fixture conformance — a self-contained validator that encodes the doc's IR @@ -243,14 +242,14 @@ function validateCompilePhaseIR(ir: unknown): string[] { // 1) Doc conformance // --------------------------------------------------------------------------- -describe("compiler/ir-v0.md — carries compile-phase outputs (delta.md §A5/§B6)", () => { +describe("compiler/ir-v0.md — carries compile-phase outputs", () => { it("declares the compile-phase IR kind, not the judge-era repository IR", () => { expect(doc()).toContain('"kind": "openprose.compile-phase-ir"'); expect(doc()).not.toContain("openprose.repository-ir"); }); it("documents the four compile-phase output sections", () => { - // architecture.md §6.3 / §3.2 / §3.3 / §6.1. + // Topology, canonicalizers, postconditions, contract fingerprints. const f = flat(); expect(f).toContain("## Topology"); expect(f).toContain("## Canonicalizers"); @@ -258,7 +257,7 @@ describe("compiler/ir-v0.md — carries compile-phase outputs (delta.md §A5/§B expect(f).toContain("## Contract Fingerprints"); }); - it("deletes the judge-era manifest concepts entirely (delta.md Part F 'IR shape')", () => { + it("deletes the judge-era manifest concepts entirely", () => { const f = flat(); // The retired manifest's per-system Forme object and its activation linkage. expect(f).not.toContain("formeManifests"); @@ -271,7 +270,7 @@ describe("compiler/ir-v0.md — carries compile-phase outputs (delta.md §A5/§B expect(f).not.toMatch(/"activations"\s*:/); }); - it("forbids the retired system and service source kinds (plan.md §3)", () => { + it("forbids the retired system and service source kinds", () => { const f = flat(); expect(f).toContain("There is no `system` kind and no `service` kind"); expect(f).toContain( @@ -279,23 +278,23 @@ describe("compiler/ir-v0.md — carries compile-phase outputs (delta.md §A5/§B ); }); - it("pins the memo key to (contract_fingerprint, input_fingerprints) only (world-model.md §4)", () => { + it("pins the memo key to (contract_fingerprint, input_fingerprints) only", () => { expect(flat()).toContain( "(contract_fingerprint, input_fingerprints)", ); }); - it("states commit-gating is validators + render self-attestation, not a judge (architecture.md §3.3)", () => { + it("states commit-gating is validators + render self-attestation, not a judge", () => { const f = flat(); expect(f).toContain("render self-attestation"); expect(f).toContain("there is no LLM in the wake/commit decision"); }); - it("declares the @atomic reserved whole-truth facet (architecture.md §6.1)", () => { + it("declares the @atomic reserved whole-truth facet", () => { expect(flat()).toContain('"@atomic"'); }); - it("documents the ####-part → facet lowering: name a part, get a facet (architecture.md §3.2, delta.md Part G)", () => { + it("documents the ####-part → facet lowering: name a part, get a facet", () => { const f = flat(); // A #### sub-heading inside ### Maintains IS a facet (the named-parts rule). expect(f).toContain("`####` sub-heading inside `### Maintains` **is a facet**"); @@ -310,6 +309,43 @@ describe("compiler/ir-v0.md — carries compile-phase outputs (delta.md §A5/§B }); }); +describe("compiler/ir-v0.md — node identity and artifact locators", () => { + it("states that `node` is mount identity, defaulting to the slug for a single mount", () => { + const f = flat(); + expect(f).toContain("### Node identity"); + expect(f).toContain("`node` is **mount identity**"); + // The default every worked example and every compile of src/ alone + // produces: one mount per contract, keyed by the contract's slug. + expect(f).toContain("defaults it to the contract's slug (frontmatter `name:`)"); + // Multi-mount and per-entity fan-out keys are the harness's to assign; + // the compiler emits the family, never an enumeration. + expect(f).toContain("the per-mount node keys are the harness's to assign"); + expect(f).toContain("the compiler emits one node per contract and never an enumeration"); + }); + + it("ties a node to its source through contract_fingerprint, and keeps `id:` out of the v2 manifest", () => { + const f = flat(); + expect(f).toContain("`contract_fingerprint` is what ties a node to its source"); + // `id:` is source identity behind the node when an author declares it; the + // node record does not carry it, so no fixture or manifest changes shape. + expect(f).toContain("Frontmatter `id:`, when an author declares it, is the source identity"); + expect(f).toContain("the node record has no `id` field"); + // The retired single-identity rule must not resurface here. + expect(f).not.toMatch(/never derive identity from/i); + }); + + it("resolves `artifact` locators against the OpenProse root, for canonicalizers and postconditions alike", () => { + const f = flat(); + expect(f).toContain("### Artifact locators"); + expect(f).toContain("`artifact` is a locator, not a payload"); + expect(f).toContain("the parent of the compile output directory"); + expect(f).toContain("`/dist/canonicalizers/competitor-monitor.js`"); + // The run phase resolves the locator when it loads the active manifest. + expect(f).toContain("when it loads `dist/manifest.active.json`"); + expect(f).toContain("The same rule applies to the `artifact` field of every `postconditions` entry"); + }); +}); + // --------------------------------------------------------------------------- // 2) Fixture conformance // --------------------------------------------------------------------------- @@ -347,7 +383,7 @@ describe("expected/stargazer.manifest.next.json — the mounted-DAG shape", () = }; }; - it("folds the former fulfillment-system services into kind: function (delta.md §B7)", () => { + it("folds the former fulfillment-system services into kind: function", () => { const fnCount = ir.sources.filter((s) => s.kind === "function").length; expect(fnCount).toBe(5); // No system/service kinds survive. @@ -384,7 +420,7 @@ describe("expected/ambiguous-fulfillment.manifest.next.json — surfaced wiring expect(ir.topology.edges).toEqual([]); }); - it("surfaces a wiring-ambiguity warning instead of a fulfillment guess (architecture.md §3.1)", () => { + it("surfaces a wiring-ambiguity warning instead of a fulfillment guess", () => { const warn = ir.diagnostics.find((d) => d.severity === "warning"); expect(warn).toBeDefined(); expect(warn?.message).toMatch(/multiple producers/i); @@ -392,8 +428,8 @@ describe("expected/ambiguous-fulfillment.manifest.next.json — surfaced wiring }); describe("expected/multi-facet.manifest.next.json — the named-parts (####) lowering", () => { - // The canonical multi-facet node (architecture.md §3.2 L173–L197 worked - // competitor-activity-monitor): `#### funding` / `#### hiring` / + // The canonical multi-facet node (the worked competitor-activity-monitor + // example in contract-markdown.md): `#### funding` / `#### hiring` / // `#### product-launches` lower to one facet each, names = the heading text; // shared un-facetted fields move only the atomic token; a subscriber that // `### Requires` *funding* draws an edge carrying ONLY the funding facet. @@ -428,16 +464,15 @@ describe("expected/multi-facet.manifest.next.json — the named-parts (####) low it("lowers an atomic-only ### Maintains (no #### parts) to facets:[@atomic] — the free default", () => { // `funding-brief` declares no #### parts, so its CanonicalizationSpec has // facets:[] and the canonicalizer emits the lone atomic facet — byte - // identical to the pre-facet leaf case (delta.md Part G L578–L579, - // architecture.md §3.2 L171). + // identical to the pre-facet leaf case. const brief = ir.canonicalizers.find((c) => c.node === "funding-brief"); expect(brief?.facets).toEqual([ATOMIC_FACET]); }); it("draws a facet-granular edge: the funding subscriber consumes only the funding facet", () => { - // Requires. ↔ Maintains. (architecture.md §6.3): the edge + // Requires. ↔ Maintains.: the edge // names `funding`, NOT `@atomic` — a move in hiring/product-launches must - // not wake this subscriber (the selector boundary, world-model.md §3). + // not wake this subscriber (the selector boundary). expect(ir.topology.edges).toEqual([ { subscriber: "funding-brief", diff --git a/tests/open-prose/compiler/fixtures/multi-facet/competitor-monitor.prose.md b/tests/open-prose/compiler/fixtures/multi-facet/competitor-monitor.prose.md index e322650f..0f0e3d7f 100644 --- a/tests/open-prose/compiler/fixtures/multi-facet/competitor-monitor.prose.md +++ b/tests/open-prose/compiler/fixtures/multi-facet/competitor-monitor.prose.md @@ -2,7 +2,7 @@ name: competitor-monitor kind: responsibility version: 0.15.0 -id: 067NC4KG01RG50R40M30E2FACE7 +id: 067NC4KG01RG50R40M3E2FACE7 --- # Competitor Activity Monitor diff --git a/tests/open-prose/compiler/fixtures/multi-facet/funding-brief.prose.md b/tests/open-prose/compiler/fixtures/multi-facet/funding-brief.prose.md index cc0e814e..f73b88f0 100644 --- a/tests/open-prose/compiler/fixtures/multi-facet/funding-brief.prose.md +++ b/tests/open-prose/compiler/fixtures/multi-facet/funding-brief.prose.md @@ -2,7 +2,7 @@ name: funding-brief kind: responsibility version: 0.15.0 -id: 067NC4KG01RG50R40M30E2BR1EF0 +id: 067NC4KG01RG50R40ME2BR1EF0 --- # Funding Brief diff --git a/tests/open-prose/concepts/concepts.test.ts b/tests/open-prose/concepts/concepts.test.ts index 0c623ff7..13047771 100644 --- a/tests/open-prose/concepts/concepts.test.ts +++ b/tests/open-prose/concepts/concepts.test.ts @@ -4,8 +4,8 @@ // These assert the docs embody the Intelligent React end-state — the // render-atom / world-model=DOM / subscriptions=props / receipt=setState / // reconciler=runtime model — and that judge-centric language is gone -// (delta.md Part B §B4/§B6; architecture.md §1/§2/§4/§6/§7; world-model.md -// §1/§2/§3/§5/§6/§8). Doc-conformance style: read the source doc, assert on +// (the run-phase model concepts/reconciler.md and concepts/responsibility.md +// define). Doc-conformance style: read the source doc, assert on // content, no runtime. import { readFileSync } from "node:fs"; import { join } from "node:path"; @@ -20,8 +20,8 @@ function doc(name: string): string { } // The retired judge-era vocabulary that must not survive in the run-phase -// concept docs (delta.md §B4: "All of this is retired (no judge, no status -// enum, no pressure, no fulfillment activation)"). +// concept docs: no judge, no status enum, no pressure, no fulfillment +// activation. const RETIRED_TERMS = [ "judge drift", "judge activation", @@ -34,11 +34,11 @@ const RETIRED_TERMS = [ "recommended activation", ]; -describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md §4)", () => { +describe("reconciler.md — the dumb reconciler", () => { const source = doc("reconciler.md"); it("frames the React mapping: world-model=DOM, subscriptions=props, receipt=setState, reconciler=runtime", () => { - // world-model.md §1 (L19): the world-model is the node's "DOM". + // The world-model is the node's "DOM". expect(source).toContain("world-model"); expect(source).toContain("the **world-model**"); expect(source).toContain("**subscriptions**"); @@ -46,21 +46,21 @@ describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md expect(source).toContain("the **reconciler**"); }); - it("declares the two phases: intelligent compile, dumb run (architecture.md §2)", () => { - // architecture.md §2 (L84-92): compile intelligent / run dumb. + it("declares the two phases: intelligent compile, dumb run", () => { + // compile intelligent / run dumb. expect(source).toMatch(/compile[\s\S]*intelligent/i); expect(source).toMatch(/run[\s\S]*dumb/i); expect(source).toContain("contract set"); }); - it("states the render atom signature (architecture.md §1 L26-27)", () => { + it("states the render atom signature", () => { expect(source).toContain( "(contract, evidence, prior world-model) -> (new world-model, receipt)", ); }); - it("declares the three wake sources as one event (architecture.md §4.2, world-model.md §5)", () => { - // world-model.md §5 (L233-238): input / self / external. + it("declares the three wake sources as one event", () => { + // input / self / external. expect(source).toContain("`input`"); expect(source).toContain("`self`"); expect(source).toContain("`external`"); @@ -69,19 +69,19 @@ describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md }); it("makes the memo key exactly (contract_fingerprint, input_fingerprints) — nothing else", () => { - // world-model.md §4 (L195): "nothing else". + // The memo key is the pair and "nothing else". expect(source).toContain("(contract_fingerprint, input_fingerprints)"); expect(source).toMatch(/nothing else/i); expect(source).toMatch(/no judge/i); }); - it("requires single-flight + coalescing and the React batching analogy (world-model.md §8)", () => { + it("requires single-flight + coalescing and the React batching analogy", () => { expect(source).toMatch(/single-flight/i); expect(source).toMatch(/coalesc/i); expect(source).toMatch(/dirty/i); }); - it("propagates only rendered-with-a-moved-fingerprint (world-model.md §8 L329-330)", () => { + it("propagates only rendered-with-a-moved-fingerprint", () => { expect(source).toMatch( /only\s+`?rendered`?\s+with a moved fingerprint propagates/i, ); @@ -90,7 +90,7 @@ describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md expect(source).toContain("`failed`"); }); - it("lists the receipt fields including fingerprints map and semantic_diff (architecture.md §6.1)", () => { + it("lists the receipt fields including fingerprints map and semantic_diff", () => { for (const field of [ "`node`", "`contract_fingerprint`", @@ -105,16 +105,29 @@ describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md ]) { expect(source).toContain(field); } - // semantic_diff is render input, never a wake signal (world-model.md §3 L174). + // semantic_diff is render input, never a wake signal. expect(source).toMatch(/never a wake signal/i); }); - it("states the structured-backing rule (world-model.md §3 L167-172)", () => { + it("gives the receipt's cost block its sub-shape: fresh vs reused tokens and a surprise_cause equal to wake.source", () => { + expect(source).toContain( + "`{ provider, model, tokens: { fresh, reused }, surprise_cause }`", + ); + expect(source).toContain("`tokens.fresh`"); + expect(source).toContain("`tokens.reused`"); + expect(source).toContain("`surprise_cause`"); + // The cause of the spend is the wake source — the observable link between + // surprise and cost. + expect(source).toMatch(/`surprise_cause`[^|]*must equal `wake\.source`/); + expect(source).toMatch(/`skipped` receipt carries zero cost/i); + }); + + it("states the structured-backing rule", () => { expect(source).toMatch(/structured[- ]backing/i); expect(source).toMatch(/render prose \*from\* it/i); }); - it("explicitly retires the judge/status/pressure/fulfillment loop (delta.md §B4)", () => { + it("explicitly retires the judge/status/pressure/fulfillment loop", () => { // The doc must call out that there is no judge and name the retired model. expect(source).toMatch(/no judge/i); expect(source).toMatch(/no status enum/i); @@ -133,21 +146,21 @@ describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md }); }); -describe("responsibility.md — mounted reactive node (delta.md §B6, architecture.md §7)", () => { +describe("responsibility.md — mounted reactive node", () => { const source = doc("responsibility.md"); - it("reframes a responsibility as a mounted node in the responsibility DAG (architecture.md §7.1)", () => { + it("reframes a responsibility as a mounted node in the responsibility DAG", () => { expect(source).toMatch(/mounted node/i); expect(source).toContain("### Requires"); expect(source).toContain("### Maintains"); }); - it("adds the ### Requires / ### Maintains reactive interface (delta.md §B2)", () => { - // delta.md §B2: responsibility gains ### Requires + ### Maintains. + it("adds the ### Requires / ### Maintains reactive interface", () => { + // A responsibility's interface is ### Requires + ### Maintains. expect(source).toContain("`Requires. ↔ Maintains.`"); }); - it("teaches ### Maintains as the four-job schema (world-model.md §2 L61-77)", () => { + it("teaches ### Maintains as the four-job schema", () => { expect(source).toMatch(/four jobs/i); expect(source).toMatch(/\*\*Type\*\*/); expect(source).toMatch(/\*\*Canonicalization spec\*\*/); @@ -157,14 +170,14 @@ describe("responsibility.md — mounted reactive node (delta.md §B6, architectu expect(source).toMatch(/false friend/i); }); - it("reshapes ### Continuity into a structural wake-source declaration (delta.md §B2, world-model.md §6)", () => { + it("reshapes ### Continuity into a structural wake-source declaration", () => { expect(source).toMatch(/wake-source declaration/i); expect(source).toMatch(/input-driven/); expect(source).toMatch(/self-driven/); expect(source).toMatch(/external-driven/); }); - it("folds Criteria/Constraints/Memory/Fulfillment per the crosswalk (delta.md §B2)", () => { + it("folds Criteria/Constraints/Memory/Fulfillment per the crosswalk", () => { expect(source).toContain("### Criteria"); expect(source).toContain("### Memory"); expect(source).toContain("### Fulfillment"); @@ -173,13 +186,13 @@ describe("responsibility.md — mounted reactive node (delta.md §B6, architectu expect(source).toMatch(/one world-model per node/i); }); - it("denies the system kind and judge file (delta.md §B1, plan.md §3)", () => { + it("denies the system kind and judge file", () => { expect(source).toMatch(/no\s+`?system`? kind/i); expect(source).toMatch(/no judge runtime exists/i); }); it("describes the compile phase output (Forme topology + canonicalizer + validators)", () => { - // architecture.md §3.1-§3.3. + // The three compile artifacts: topology, canonicalizers, validators. expect(source).toMatch(/topology world-model/i); expect(source).toMatch(/canonicalizer/i); expect(source).toMatch(/postcondition validators/i); @@ -196,7 +209,7 @@ describe("responsibility.md — mounted reactive node (delta.md §B6, architectu }); }); -describe("concepts/README.md — index refresh (delta.md §B6 KEEP)", () => { +describe("concepts/README.md — index refresh", () => { const source = doc("README.md"); it("describes the reconciler as the dumb reconciler, not the pressure loop", () => { diff --git a/tests/open-prose/contract-markdown/contract-markdown.test.ts b/tests/open-prose/contract-markdown/contract-markdown.test.ts index fa7a1b3f..5bbd69be 100644 --- a/tests/open-prose/contract-markdown/contract-markdown.test.ts +++ b/tests/open-prose/contract-markdown/contract-markdown.test.ts @@ -1,8 +1,8 @@ // Conformance test for the format-defining SKILL doc, contract-markdown.md. // -// This asserts the doc embodies the Intelligent React end-state (delta.md Part B, -// §B1 kinds + §B2 sections; plan.md §3-§5; world-model.md §2/§5/§6; -// architecture.md §7). It is a doc-conformance test — it reads the source doc +// This asserts the doc embodies the Intelligent React end-state (the five kinds, +// the canonical sections, the four-job Maintains, the wake-source Continuity). +// It is a doc-conformance test — it reads the source doc // and asserts on its content, no runtime. import { readFileSync } from "node:fs"; import { join } from "node:path"; @@ -28,10 +28,10 @@ function body(): string { return source.slice(end + 4); } -describe("contract-markdown format doc — kinds (delta.md §B1)", () => { +describe("contract-markdown format doc — kinds", () => { it("declares exactly the five ideal kinds in the frontmatter spec", () => { - // architecture.md §7.1 / plan.md §3: responsibility, function, gateway, - // pattern, test. (Shown in the ## Frontmatter section's kind: enum.) + // responsibility, function, gateway, pattern, test. (Shown in the + // ## Frontmatter section's kind: enum.) expect(doc()).toContain( "responsibility | function | gateway | pattern | test", ); @@ -39,7 +39,7 @@ describe("contract-markdown format doc — kinds (delta.md §B1)", () => { it("deletes the retired service and system kinds from the kind enum", () => { const source = doc(); - // delta.md §B1: service -> function; system -> deleted. + // service -> function; system -> deleted. expect(source).not.toMatch( /kind:\s*service\s*\|/, ); @@ -53,53 +53,53 @@ describe("contract-markdown format doc — kinds (delta.md §B1)", () => { }); it("states there is no system kind and gives the composition replacement", () => { - // plan.md §3 L105: system is deleted; composition is intra-node call or - // cross-node subscription, never a third autowired graph kind. + // system is deleted; composition is intra-node call or cross-node + // subscription, never a third autowired graph kind. expect(flat()).toMatch(/no `system` kind/); expect(flat()).toMatch(/never a third/i); }); it("names function as the replacement for the retired service", () => { - // plan.md §6 L147 / delta.md §B1: function replaces service. + // function replaces service. expect(flat()).toMatch(/replacement for the retired `service`/); }); it("frames gateway as sugar for an external-driven responsibility", () => { - // delta.md §B1 / plan.md §3 L94: gateway = external-driven responsibility. + // gateway = external-driven responsibility. expect(flat()).toMatch(/sugar for an external-driven responsibility/i); expect(doc()).toContain("### Continuity: external-driven"); }); it("frames every kind as sugar over one render atom", () => { - // plan.md §1 L71: kind is sugar over the one render atom. + // kind is sugar over the one render atom. expect(flat()).toMatch(/sugar over (that|the) (single )?render atom/i); }); it("anchors node-ness in mounting, not statefulness", () => { - // plan.md §2 L74 / architecture.md §1 L34: mounting makes a node. + // Mounting makes a node. expect(flat()).toMatch(/mounted as a subscribable producer/); expect(flat()).toMatch(/not.+because it holds state/i); }); }); -describe("contract-markdown format doc — sections (delta.md §B2)", () => { +describe("contract-markdown format doc — sections", () => { it("introduces the data-flow interface ### Requires -> ### Maintains", () => { const source = doc(); - // world-model.md §2; delta.md §B2: Ensures -> Maintains. + // Ensures -> Maintains. expect(source).toContain("### Maintains"); expect(source).toContain("### Requires"); }); it("introduces the function interface ### Parameters -> ### Returns", () => { const source = doc(); - // architecture.md §7.2; plan.md §4 L112. + // Callables declare Parameters -> Returns. expect(source).toContain("### Parameters"); expect(source).toContain("### Returns"); }); it("retires ### Ensures as a live section (only shown as a folded-from legacy)", () => { const source = doc(); - // delta.md §B2: Ensures renamed to Maintains. It may appear only in the + // Ensures was re-purposed as Maintains. It may appear only in the // fold table, never as an authored section. const canonicalTable = source.slice( source.indexOf("## Canonical Sections"), @@ -110,8 +110,7 @@ describe("contract-markdown format doc — sections (delta.md §B2)", () => { it("documents ### Maintains as the four-job world-model schema", () => { const source = doc(); - // world-model.md §2 L62-L77: type, canonicalization spec, facets, - // postconditions. + // The four jobs: type, canonicalization spec, facets, postconditions. const m = source.slice(source.indexOf("## Maintains")); expect(m).toMatch(/four jobs/i); expect(m).toMatch(/canonicaliz/i); @@ -121,14 +120,14 @@ describe("contract-markdown format doc — sections (delta.md §B2)", () => { }); it("carries the structured-backing rule for subscribed truth", () => { - // world-model.md §3 L167-L172; architecture.md §3.2. + // Subscribed truth needs a structured, canonicalizable backing. expect(flat()).toMatch(/structured-backing rule/i); expect(flat()).toMatch(/excluded from the fingerprint/i); }); it("reshapes ### Continuity into a three-mode wake-source declaration", () => { const source = doc(); - // plan.md §4 L117; world-model.md §5; architecture.md §4.2. + // Three wake sources: input, self, external. const c = source.slice(source.indexOf("## Continuity")); expect(c).toMatch(/wake-source/i); expect(c).toMatch(/input-driven/); @@ -138,7 +137,7 @@ describe("contract-markdown format doc — sections (delta.md §B2)", () => { }); it("keeps freshness state in the world-model and freshness policy in Continuity", () => { - // world-model.md §6 L267-L283. + // Freshness state lives in the world-model; freshness policy in Continuity. expect(flat()).toMatch(/valid_until/); expect(flat()).toMatch(/Freshness \*state\*/); expect(flat()).toMatch(/Freshness \*policy\*/); @@ -146,7 +145,7 @@ describe("contract-markdown format doc — sections (delta.md §B2)", () => { it("folds the judge-era responsibility sections", () => { const source = doc(); - // delta.md §B2 / plan.md §4 L119-L131. + // The folded/deleted table names every judge-era section. const fold = source.slice(source.indexOf("### Folded and deleted sections")); expect(fold).toContain("### Criteria"); expect(fold).toContain("### Fulfillment"); @@ -158,7 +157,7 @@ describe("contract-markdown format doc — sections (delta.md §B2)", () => { it("drops ### Memory: folded into the world-model for responsibilities, gone for functions", () => { const source = doc(); - // world-model.md §9.4 L343-L347; delta.md §B2. + // One persisted world-model per node subsumes the old Memory ledger. // No live ### Memory authoring section remains. expect(source).not.toContain("## Memory\n"); expect(flat()).toMatch(/single persisted world-model/); @@ -168,7 +167,7 @@ describe("contract-markdown format doc — sections (delta.md §B2)", () => { it("deletes ### Services and ### Wiring as live sections (system is gone)", () => { const source = doc(); - // delta.md §B2; plan.md §3 L105. + // Services and Wiring left with the system kind. const canonicalTable = source.slice( source.indexOf("## Canonical Sections"), source.indexOf("### Folded and deleted sections"), @@ -179,20 +178,20 @@ describe("contract-markdown format doc — sections (delta.md §B2)", () => { it("keeps the carried-stable host-capability sections", () => { const source = doc(); - // architecture.md §7.2 L295: Shape/Environment/Tools/Runtime carried. + // Shape/Environment/Tools/Runtime are carried. for (const s of ["### Shape", "### Environment", "### Tools", "### Runtime"]) { expect(source).toContain(s); } }); it("clarifies ### Shape delegates is intra-node, not a DAG edge", () => { - // delta.md Part E item 5; plan.md §7 L154. + // delegates is intra-node composition, not a subscription. expect(flat()).toMatch(/delegates.+intra-node|intra-node.+delegates/is); expect(flat()).toMatch(/not a DAG edge|not a subscription/); }); }); -describe("contract-markdown format doc — Maintains teaches #### facets (delta.md Part G)", () => { +describe("contract-markdown format doc — Maintains teaches #### facets", () => { function maintains(): string { const source = doc(); // The dedicated facet section lives under the ## Maintains *section @@ -205,8 +204,7 @@ describe("contract-markdown format doc — Maintains teaches #### facets (delta. } it("declares the named-parts rule: a #### sub-heading inside ### Maintains IS a facet", () => { - // architecture.md §3.2 ("the named-parts rule"); §10.2 (DECIDED: named parts); - // world-model.md §9.5 (RESOLVED); delta.md Part G. + // The named-parts rule: naming a part declares a facet. const m = maintains(); expect(m).toMatch(/named-parts rule/i); expect(m).toMatch(/`#### \{name\}` sub-heading inside `### Maintains`/); @@ -214,7 +212,7 @@ describe("contract-markdown format doc — Maintains teaches #### facets (delta. }); it("names the facet in three places: fingerprint unit, subscription symbol, world-model subtree", () => { - // architecture.md §3.2: "the same name in three places at once". + // "the same name in three places at once". const m = maintains(); expect(m).toMatch(/fingerprint unit/i); expect(m).toMatch(/subscription symbol/i); @@ -223,14 +221,14 @@ describe("contract-markdown format doc — Maintains teaches #### facets (delta. }); it("states naming no parts is the atomic default (the leaf-node case)", () => { - // world-model.md §9.5; architecture.md §10.2: atomic-only stays the default. + // Atomic-only stays the default. const m = maintains(); expect(m).toMatch(/atomic facet/i); expect(m).toMatch(/atomic-only.+v1 default|default.+atomic-only/is); }); it("carries the worked competitor-activity-monitor example with three #### facets", () => { - // architecture.md §3.2 worked example: funding / hiring / product-launches. + // The worked example: funding / hiring / product-launches. const m = maintains(); expect(m).toContain("#### funding"); expect(m).toContain("#### hiring"); @@ -242,16 +240,36 @@ describe("contract-markdown format doc — Maintains teaches #### facets (delta. }); it("documents the Requires. <-> Maintains. symmetry and the unchanged memo key", () => { - // architecture.md §6.3 (edges) + delta.md Part G (memo key unchanged). + // Edges join on the facet name; the memo key is unchanged. const m = maintains().replace(/\s+/g, " "); expect(m).toMatch(/Requires\..+Maintains\./); expect(m).toMatch(/memo key is unchanged/i); expect(m).toMatch(/\(contract_fingerprint, input_fingerprints\)/); }); + it("documents facet families: the contract declares the family, a harness instantiates the members", () => { + const m = maintains(); + expect(m).toMatch(/^### Facet families and per-entity mounts$/m); + // A placeholder heading declares one facet per entity, shape shared. + expect(m).toMatch(/`#### user:`/); + expect(m.replace(/\s+/g, " ")).toMatch(/declares one facet per entity/); + // A placeholder in a facet-need subscribes to one member of the family. + expect(m).toMatch(/`session:`/); + expect(m.replace(/\s+/g, " ")).toMatch(/subscribes to one member/); + // The compiler emits the family; nothing in src/ enumerates instances. + expect(m.replace(/\s+/g, " ")).toMatch(/emits the family, never an enumeration/); + expect(m.replace(/\s+/g, " ")).toMatch(/`src\/` lists no instances/); + }); + + it("marks a bracketed title as a contract a harness mounts once per entity", () => { + const m = maintains().replace(/\s+/g, " "); + expect(m).toMatch(/`# Title \[instance\]`/); + expect(m).toMatch(/mounts this contract once per entity/); + }); + it("retires the 'inline vs sub-block — open' ergonomics caveat", () => { - // delta.md Part G: replace the L396 open-ergonomics note. The decision is - // settled (named parts), so the doc must not call the syntax open anymore. + // The decision is settled (named parts), so the doc must not call the + // syntax open anymore. const flatDoc = flat(); expect(flatDoc).not.toMatch(/inline vs a sub-block/i); expect(flatDoc).not.toMatch(/open ergonomics question/i); @@ -259,7 +277,7 @@ describe("contract-markdown format doc — Maintains teaches #### facets (delta. }); }); -describe("contract-markdown format doc — Header Hierarchy marks #### semantic (delta.md Part G)", () => { +describe("contract-markdown format doc — Header Hierarchy marks #### semantic", () => { function hierarchy(): string { const source = doc(); return source.slice( @@ -269,7 +287,7 @@ describe("contract-markdown format doc — Header Hierarchy marks #### semantic } it("marks #### inside ### Maintains as a semantic facet, not free-form documentation", () => { - // architecture.md §3.2 / §10.2: #### inside Maintains is a facet. + // #### inside Maintains is a facet. const h = hierarchy(); expect(h).toMatch(/`####` inside `### Maintains`/); expect(h).toMatch(/Semantic: a facet/i); @@ -279,7 +297,7 @@ describe("contract-markdown format doc — Header Hierarchy marks #### semantic }); it("marks #### inside ### Requires as a semantic facet-need", () => { - // architecture.md §6.3: Requires. is the subscription symbol. + // Requires. is the subscription symbol. const h = hierarchy(); expect(h).toMatch(/`####` inside `### Requires`/); expect(h).toMatch(/Semantic: a facet-need/i); @@ -295,30 +313,49 @@ describe("contract-markdown format doc — Header Hierarchy marks #### semantic describe("contract-markdown format doc — composition + render body", () => { it("keeps ### Execution as the intra-node ProseScript render body", () => { - // plan.md §7; architecture.md §7.2. + // Execution is the intra-node render body; none of it is a node. expect(doc()).toContain("### Execution"); expect(flat()).toMatch(/render body/i); expect(flat()).toMatch(/none of it is a node/i); }); it("describes intra-node call and cross-node subscription as the two composition forms", () => { - // plan.md §3/§5. + // The two composition forms. expect(flat()).toMatch(/imperative `call`/); expect(flat()).toMatch(/cross-node \*?subscription\*?/); }); it("matches Requires<->Maintains via Forme semantically", () => { - // plan.md §5 L132-L137; world-model.md §5 L256. + // Forme matches the need to the producer semantically. expect(flat()).toMatch(/Requires.+Maintains/); expect(flat()).toMatch(/semantically/); }); }); describe("contract-markdown format doc — frontmatter + identity", () => { - it("requires id frontmatter only on responsibilities", () => { + it("makes id frontmatter optional on responsibilities and gateways, with the slug as default identity", () => { + expect(flat()).toMatch( + /A `kind: responsibility` or `kind: gateway` file may declare `id:` frontmatter/, + ); + expect(flat()).toMatch(/Without one, the slug is the identity/); + expect(flat()).not.toMatch(/declares required `id:`/); + }); + + it("spells out the rendered id format and the tool that mints one", () => { + // The corpus identity suite asserts exactly this shape on every id present, + // so the doc and the test cannot disagree about what a well-formed id is. + expect(flat()).toMatch( + /26 characters of uppercase Crockford base32 \(`0-9A-HJKMNP-TV-Z`\)/, + ); + expect(flat()).toMatch(/`scripts\/mint-contract-id\.mjs`/); + expect(flat()).toMatch(/never hand-typed/); + }); + + it("documents version: as optional author-owned provenance the compiler ignores", () => { expect(flat()).toMatch( - /A `kind: responsibility` file also declares required `id:`/, + /`version:` is optional, author-owned provenance in semver form/, ); + expect(flat()).toMatch(/not the skill version/); }); it("requires subject frontmatter on tests, naming a responsibility or function", () => { diff --git a/tests/open-prose/examples-corpus/canonical-sections.test.ts b/tests/open-prose/examples-corpus/canonical-sections.test.ts new file mode 100644 index 00000000..0fa6bf0e --- /dev/null +++ b/tests/open-prose/examples-corpus/canonical-sections.test.ts @@ -0,0 +1,171 @@ +// Conformance test for canonical `###` sections across the whole examples +// corpus. +// +// skills/open-prose/contract-markdown.md (## Canonical Sections) lists the +// `###` sections Forme and the Prose VM recognize, case-insensitively. Any +// other `###` heading is preserved as documentation and is not a contract +// section, so a near-miss such as `### Continuity: external-driven` or +// `### Postconditions` silently lowers to prose instead of carrying the +// semantics its author meant. This suite holds every contract under +// examples/*/src/ to the canonical table, and holds the table it hardcodes +// to the format doc, so the two cannot drift apart. +// +// Like the identity suite, it finds its targets by walking the tree, so a new +// example is covered the day it lands and cannot be silently unchecked. +// +// It is a doc-conformance test: it reads the source `.prose.md` files and +// asserts on their content; no runtime. +// +// RUN: npx vitest run tests/open-prose/examples-corpus +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { join, relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const repoRoot = fileURLToPath(new URL("../../../", import.meta.url)); +const examplesDir = join(repoRoot, "skills/open-prose/examples"); +const formatDoc = join(repoRoot, "skills/open-prose/contract-markdown.md"); + +// The 26 canonical `###` sections, exactly as contract-markdown.md tables them. +// Hardcoded so this file reads standalone; the first test cross-checks it +// against the doc. +const CANONICAL = [ + "Description", + "Goal", + "Requires", + "Maintains", + "Parameters", + "Returns", + "Continuity", + "Errors", + "Invariants", + "Strategies", + "Environment", + "Runtime", + "Skills", + "Tools", + "Shape", + "Execution", + "Fixtures", + "Expects", + "Expects Not", + "Slots", + "Config", + "Delegation", + "Schedule", + "Receives", + "Emits", + "Payload", +]; + +// Matching is case-insensitive, as the format doc says the VM's is. +const CANONICAL_SET = new Set(CANONICAL.map((s) => s.toLowerCase())); + +// Legitimate exceptions, keyed by repo-relative path, each heading with a +// comment saying why it is allowed. Prefer folding a heading into a canonical +// section over listing it here; the list exists so an exception is explicit, +// never a weakened assertion. +const ALLOWLIST: Record = {}; + +// Recursively collect every authored contract under examples/*/src/. +function proseFilesUnder(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + out.push(...proseFilesUnder(full)); + } else if (entry.endsWith(".prose.md")) { + out.push(full); + } + } + return out; +} + +function exampleDirs(): string[] { + return readdirSync(examplesDir) + .map((name) => join(examplesDir, name)) + .filter((abs) => statSync(abs).isDirectory() && existsSync(join(abs, "src"))); +} + +function corpus(): string[] { + const out: string[] = []; + for (const ex of exampleDirs()) out.push(...proseFilesUnder(join(ex, "src"))); + return out.sort(); +} + +function read(abs: string): string { + return readFileSync(abs, "utf8"); +} +function rel(abs: string): string { + return relative(repoRoot, abs); +} + +// Every `###` heading in a contract (never `####`), skipping fenced code so a +// heading quoted inside an example block is not counted as a section. +function sectionHeadings(source: string): string[] { + const out: string[] = []; + let fenced = false; + for (const line of source.split("\n")) { + if (/^\s*(```|~~~)/.test(line)) { + fenced = !fenced; + continue; + } + if (fenced) continue; + const m = /^###\s+([^#].*?)\s*$/.exec(line); + if (m) out.push(m[1]); + } + return out; +} + +// The section names the format doc tables under ## Canonical Sections. +function documentedSections(): string[] { + const doc = read(formatDoc); + const start = doc.indexOf("\n## Canonical Sections"); + expect(start, "contract-markdown.md has a ## Canonical Sections heading").toBeGreaterThan(-1); + const rest = doc.slice(start + 1); + const next = rest.search(/\n##+ /); + const section = next === -1 ? rest : rest.slice(0, next); + const names: string[] = []; + for (const m of section.matchAll(/^\| `### ([^`]+)` \|/gm)) names.push(m[1]); + return names; +} + +const ALL = corpus(); + +describe("examples corpus — the canonical section table", () => { + it("matches the table in contract-markdown.md, name for name", () => { + const documented = documentedSections(); + expect(documented.length).toBe(CANONICAL.length); + expect([...documented].sort()).toEqual([...CANONICAL].sort()); + }); + + it("walks every example directory that has a src/ and finds its contracts", () => { + expect(ALL.length).toBeGreaterThan(0); + }); +}); + +describe("examples corpus — every ### heading is a canonical section", () => { + it("uses only sections Forme and the VM recognize", () => { + const offenders: string[] = []; + for (const f of ALL) { + const allowed = new Set(ALLOWLIST[rel(f)] ?? []); + for (const heading of sectionHeadings(read(f))) { + if (CANONICAL_SET.has(heading.toLowerCase())) continue; + if (allowed.has(heading)) continue; + offenders.push(`${rel(f)} -> ### ${heading}`); + } + } + expect(offenders, offenders.join("\n")).toEqual([]); + }); + + it("keeps the allowlist honest: every listed exception still exists", () => { + // An entry that no longer matches a real heading is stale and should go. + const stale: string[] = []; + for (const [file, headings] of Object.entries(ALLOWLIST)) { + const abs = join(repoRoot, file); + const present = existsSync(abs) ? new Set(sectionHeadings(read(abs))) : new Set(); + for (const h of headings) if (!present.has(h)) stale.push(`${file} -> ### ${h}`); + } + expect(stale, stale.join("\n")).toEqual([]); + }); +}); diff --git a/tests/open-prose/examples-corpus/contract-identity.test.ts b/tests/open-prose/examples-corpus/contract-identity.test.ts new file mode 100644 index 00000000..5ffb3b9e --- /dev/null +++ b/tests/open-prose/examples-corpus/contract-identity.test.ts @@ -0,0 +1,116 @@ +// Conformance test for contract identity across the whole examples corpus. +// +// skills/open-prose/contract-markdown.md (## Frontmatter) lets a +// `kind: responsibility` or `kind: gateway` file declare `id:` for an identity +// that survives renames; without one, the slug is the identity. An id, when +// present, is a UUIDv7-compatible value rendered as 26 characters of uppercase +// Crockford base32, minted by tooling and never hand-typed. `version:` is +// optional author-owned provenance in semver form. This suite holds every +// contract under examples/*/src/ to those rules: presence is never required, +// but whatever is present must be well-formed and unique. +// +// Unlike the shape suites, which name the examples they own, this suite finds +// its targets by walking the tree, so a new example is covered the day it +// lands and cannot be silently unchecked. +// +// It is a doc-conformance test: it reads the source `.prose.md` files and +// asserts on their content; no runtime. +// +// RUN: npx vitest run tests/open-prose/examples-corpus +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { join, relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const repoRoot = fileURLToPath(new URL("../../../", import.meta.url)); +const examplesDir = join(repoRoot, "skills/open-prose/examples"); + +// The rendered id shape from contract-markdown.md: 26 Crockford base32 +// characters (no I, L, O, U), uppercase, on its own frontmatter line. +const ID = /^id:\s*([0-9A-HJKMNP-TV-Z]{26})\s*$/m; +const ANY_ID = /^id:\s*(.*?)\s*$/m; +const SEMVER = /^version:\s*\d+\.\d+\.\d+\s*$/m; +const ANY_VERSION = /^version:\s*(.*?)\s*$/m; + +// Recursively collect every authored contract under examples/*/src/. +function proseFilesUnder(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + out.push(...proseFilesUnder(full)); + } else if (entry.endsWith(".prose.md")) { + out.push(full); + } + } + return out; +} + +function exampleDirs(): string[] { + return readdirSync(examplesDir) + .map((name) => join(examplesDir, name)) + .filter((abs) => statSync(abs).isDirectory() && existsSync(join(abs, "src"))); +} + +function corpus(): string[] { + const out: string[] = []; + for (const ex of exampleDirs()) out.push(...proseFilesUnder(join(ex, "src"))); + return out.sort(); +} + +function read(abs: string): string { + return readFileSync(abs, "utf8"); +} +function frontmatter(abs: string): string { + const source = read(abs); + const end = source.indexOf("\n---", 3); + return source.slice(0, end + 4); +} +function rel(abs: string): string { + return relative(repoRoot, abs); +} + +const ALL = corpus(); + +describe("examples corpus — discovery covers every example by construction", () => { + it("walks every example directory that has a src/ and finds its contracts", () => { + const empty = exampleDirs().filter( + (ex) => proseFilesUnder(join(ex, "src")).length === 0, + ); + expect(empty.map(rel), empty.map(rel).join("\n")).toEqual([]); + expect(ALL.length).toBeGreaterThan(0); + }); +}); + +describe("examples corpus — contract identity", () => { + it("every id: in the corpus, on any kind, is well-formed", () => { + // Presence is optional, but an id that is present must be the documented + // 26-character shape, not a slug or a hand-typed string. + const offenders = ALL.filter((f) => { + const fm = frontmatter(f); + return ANY_ID.test(fm) && !ID.test(fm); + }).map((f) => `${rel(f)} -> ${ANY_ID.exec(frontmatter(f))?.[0]}`); + expect(offenders, offenders.join("\n")).toEqual([]); + }); + + it("ids are unique across the corpus", () => { + const seen = new Map(); + for (const f of ALL) { + const m = ID.exec(frontmatter(f)); + if (!m) continue; + seen.set(m[1], [...(seen.get(m[1]) ?? []), rel(f)]); + } + const duplicates = [...seen.entries()] + .filter(([, files]) => files.length > 1) + .map(([id, files]) => `${id}: ${files.join(", ")}`); + expect(duplicates, duplicates.join("\n")).toEqual([]); + }); + + it("version:, when present, is semver", () => { + const offenders = ALL.filter((f) => { + const fm = frontmatter(f); + return ANY_VERSION.test(fm) && !SEMVER.test(fm); + }).map((f) => `${rel(f)} -> ${ANY_VERSION.exec(frontmatter(f))?.[0]}`); + expect(offenders, offenders.join("\n")).toEqual([]); + }); +}); diff --git a/tests/open-prose/examples-corpus/described-topologies.test.ts b/tests/open-prose/examples-corpus/described-topologies.test.ts new file mode 100644 index 00000000..70ef2961 --- /dev/null +++ b/tests/open-prose/examples-corpus/described-topologies.test.ts @@ -0,0 +1,212 @@ +// Conformance test for described topologies across the whole examples corpus. +// +// Several example READMEs sketch a topology larger than their own src/ can +// mount: one authored contract stands for a family of nodes (one per package, +// per email, per session, ...) that a harness instantiates at mount time. The +// corpus keeps those numbers, but a README may not state a node count that +// mounting src/ alone cannot produce without saying who produces it. This +// suite holds every README to that rule: +// +// - a node count larger than the example's mountable contracts must sit in +// the same sentence as an attribution to the reference harness; +// - a `## Conformance expectations` section states no node or edge count, +// because that section describes behavior any conforming harness proves, +// not the size of one implementation's expansion. +// +// "Mountable" counts the `kind: responsibility` and `kind: gateway` files +// under src/; functions are called, never mounted. A count that shares a line +// with another example's name describes that example's topology (tamper-forge +// audits the masked-relay ledger) and is measured against it instead. +// +// Like the identity and canonical-sections suites, it finds its targets by +// walking the tree, so a new example is covered the day it lands and cannot +// over-claim silently. +// +// It is a doc-conformance test: it reads the READMEs and contracts off disk +// and asserts on their content; no runtime. +// +// RUN: npx vitest run tests/open-prose/examples-corpus +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { join, relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const repoRoot = fileURLToPath(new URL("../../../", import.meta.url)); +const examplesDir = join(repoRoot, "skills/open-prose/examples"); +const corpusIndex = join(examplesDir, "README.md"); + +// The kinds Forme mounts as nodes. Everything else in src/ is called. +const MOUNTED_KINDS = new Set(["responsibility", "gateway"]); + +// The permitted name for the implementation whose expansion produces a +// described topology. The product itself is not named under examples/. +const ATTRIBUTION = /\breference harness\b/; + +// "22 nodes", "22-node", "22 node". +const NODE_CLAIM = /\b(\d+)[- ]nodes?\b/g; +// Any node or edge count, for the expectations section. +const ANY_COUNT = /\b\d+[- ](nodes?|edges?)\b/; + +const EXPECTATIONS_HEADING = /^## Conformance expectations\s*$/i; + +// Legitimate exceptions, keyed by repo-relative README path, each with a +// comment saying why. Prefer attributing a count over listing it here; the +// list exists so an exception is explicit, never a weakened assertion. +const ALLOWLIST: Record = {}; + +function read(abs: string): string { + return readFileSync(abs, "utf8"); +} +function rel(abs: string): string { + return relative(repoRoot, abs); +} +// Collapse whitespace so a sentence wrapped across lines matches as one. +function flat(s: string): string { + return s.replace(/\s+/g, " "); +} + +// Recursively collect every authored contract under a src/ directory. +function proseFilesUnder(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + out.push(...proseFilesUnder(full)); + } else if (entry.endsWith(".prose.md")) { + out.push(full); + } + } + return out; +} + +function frontmatter(source: string): string { + if (!source.startsWith("---")) return ""; + const end = source.indexOf("\n---", 3); + return end === -1 ? "" : source.slice(0, end + 4); +} + +function kindOf(abs: string): string { + const m = /^kind:\s*(\S+)/m.exec(frontmatter(read(abs))); + return m ? m[1] : ""; +} + +// Every example directory: a directory under examples/ that carries a src/. +function exampleNames(): string[] { + return readdirSync(examplesDir) + .filter((name) => { + const abs = join(examplesDir, name); + return statSync(abs).isDirectory() && existsSync(join(abs, "src")); + }) + .sort(); +} + +// How many nodes mounting src/ alone yields. +function mountsOf(name: string): number { + return proseFilesUnder(join(examplesDir, name, "src")).filter((f) => + MOUNTED_KINDS.has(kindOf(f)), + ).length; +} + +// The lines under `## Conformance expectations`, up to the next H2. +function expectationsSection(source: string): string | undefined { + const lines = source.split("\n"); + const start = lines.findIndex((line) => EXPECTATIONS_HEADING.test(line)); + if (start === -1) return undefined; + const body: string[] = []; + for (const line of lines.slice(start + 1)) { + if (line.startsWith("## ")) break; + body.push(line); + } + return body.join("\n"); +} + +type Claim = { count: number; line: number; about: string }; + +// Every node count a README states, with the example it is about: the README's +// own example unless a sibling example is named on the same line. +function nodeClaims(self: string, source: string, siblings: string[]): Claim[] { + const out: Claim[] = []; + source.split("\n").forEach((line, i) => { + const named = siblings.find((s) => s !== self && line.includes(s)); + for (const m of line.matchAll(NODE_CLAIM)) { + out.push({ count: Number(m[1]), line: i + 1, about: named ?? self }); + } + }); + return out; +} + +const NAMES = exampleNames(); +const MOUNTS = new Map(NAMES.map((name) => [name, mountsOf(name)])); + +describe("examples corpus — the walk", () => { + it("finds every example directory and its README", () => { + expect(NAMES.length).toBeGreaterThan(20); + const missing = NAMES.filter((name) => !existsSync(join(examplesDir, name, "README.md"))); + expect(missing, `examples without a README:\n${missing.join("\n")}`).toEqual([]); + }); + + it("counts mountable contracts (responsibility + gateway), never functions", () => { + // competitor-activity: one responsibility, one gateway; its functions, if + // any, are called. A wrong census here would make every claim look fine. + expect(MOUNTS.get("competitor-activity")).toBe(2); + expect(MOUNTS.get("monorepo-ci")).toBe(4); + }); +}); + +describe("examples corpus — a README does not over-claim its topology", () => { + it("attributes every node count that src/ alone cannot mount to the reference harness", () => { + const offenders: string[] = []; + for (const name of NAMES) { + const abs = join(examplesDir, name, "README.md"); + if (!existsSync(abs)) continue; + const source = read(abs); + const allowed = new Set(ALLOWLIST[rel(abs)]?.claims ?? []); + const text = flat(source); + for (const claim of nodeClaims(name, source, NAMES)) { + const mounts = MOUNTS.get(claim.about) ?? 0; + if (claim.count <= mounts) continue; + if (allowed.has(claim.count)) continue; + // The count and the attribution must share a sentence. + const attributed = new RegExp( + `\\b${claim.count}[- ]nodes?\\b[^.]*?${ATTRIBUTION.source}`, + ).test(text); + if (attributed) continue; + offenders.push( + `${rel(abs)}:${claim.line} -> claims ${claim.count} nodes; ` + + `src/ mounts ${mounts} and no sentence attributes the count to the reference harness`, + ); + } + } + expect(offenders, offenders.join("\n")).toEqual([]); + }); + + it("states no node or edge count inside ## Conformance expectations", () => { + const offenders: string[] = []; + for (const name of NAMES) { + const abs = join(examplesDir, name, "README.md"); + if (!existsSync(abs)) continue; + if (ALLOWLIST[rel(abs)]?.expectations) continue; + const section = expectationsSection(read(abs)); + if (section === undefined) continue; + const m = ANY_COUNT.exec(flat(section)); + if (m) offenders.push(`${rel(abs)} -> "${m[0]}" in ## Conformance expectations`); + } + expect(offenders, offenders.join("\n")).toEqual([]); + }); + + it("keeps the allowlist honest: every listed exception names a real README", () => { + const stale = Object.keys(ALLOWLIST).filter((file) => !existsSync(join(repoRoot, file))); + expect(stale, stale.join("\n")).toEqual([]); + }); +}); + +describe("examples corpus — the index attributes described topologies", () => { + it("## Conformance says the reference harness's expansion produces the larger counts", () => { + const source = read(corpusIndex); + const start = source.indexOf("\n## Conformance"); + expect(start, "examples/README.md has a ## Conformance heading").toBeGreaterThan(-1); + const section = flat(source.slice(start)); + expect(section).toMatch(ATTRIBUTION); + expect(section).toMatch(/described topolog/i); + }); +}); diff --git a/tests/open-prose/examples-corpus/examples-corpus-migration.test.ts b/tests/open-prose/examples-corpus/examples-corpus-migration.test.ts index 24677b21..3e93798b 100644 --- a/tests/open-prose/examples-corpus/examples-corpus-migration.test.ts +++ b/tests/open-prose/examples-corpus/examples-corpus-migration.test.ts @@ -4,7 +4,8 @@ // // This is the module-1 (skill-examples-corpus) acceptance test: it proves the // whole public learning surface was re-cleaved onto the new kinds + sections -// (delta.md Part B §B1/§B2/§B7), the `system` kind is gone, every `service` +// (the kinds and sections contract-markdown.md defines), the `system` kind is +// gone, every `service` // became a `function`, every judge-era `responsibility` gained Requires + // Maintains and dropped Criteria/Fulfillment, every gateway gained an explicit // `### Continuity: external-driven`, and the `### Memory` ledger folded into the @@ -66,18 +67,18 @@ function kindOf(abs: string): string { const ALL = proseFiles(); -describe("examples corpus — the retired kinds are gone (delta.md §B1)", () => { +describe("examples corpus — the retired kinds are gone", () => { it("declares no `service` or `system` kind in any owned example", () => { for (const f of ALL) { const fm = frontmatter(f); - // delta.md §B1 L275-L276: service -> function; system -> DELETE. + // service -> function; system -> deleted. expect(fm, f).not.toMatch(/kind:\s*service\b/); expect(fm, f).not.toMatch(/kind:\s*system\b/); } }); it("uses only the five recognized kinds (responsibility/function/gateway/pattern/test)", () => { - // architecture.md §7.1 L268-L274: the kind taxonomy. + // The kind taxonomy from contract-markdown.md's frontmatter enum. const allowed = new Set([ "responsibility", "function", @@ -91,12 +92,12 @@ describe("examples corpus — the retired kinds are gone (delta.md §B1)", () => }); }); -describe("examples corpus — the retired sections are gone (delta.md §B2)", () => { +describe("examples corpus — the retired sections are gone", () => { it("no `### Ensures`, `### Criteria`, `### Fulfillment`, `### Services`, `### Wiring`, `### Memory` headers", () => { for (const f of ALL) { const source = read(f); - // delta.md §B2 L286-L294: Ensures->Maintains/Returns; Criteria/Fulfillment - // folded; Services/Wiring deleted with system; Memory folds into the WM. + // Ensures -> Maintains/Returns; Criteria/Fulfillment folded; Services/Wiring + // deleted with system; Memory folds into the world-model. expect(source, f).not.toMatch(/^### Ensures\b/m); expect(source, f).not.toMatch(/^### Criteria\b/m); expect(source, f).not.toMatch(/^### Fulfillment\b/m); @@ -108,19 +109,19 @@ describe("examples corpus — the retired sections are gone (delta.md §B2)", () }); }); -describe("examples corpus — functions declare Parameters -> Returns (plan.md §4)", () => { +describe("examples corpus — functions declare Parameters -> Returns", () => { const functions = ALL.filter((f) => kindOf(f) === "function"); it("there is at least one migrated function (the former services)", () => { - // delta.md §B7 L373-L375: the 43 `service` files become `function`s. + // The former `service` files became `function`s. expect(functions.length).toBeGreaterThan(0); }); it("every function declares ### Returns and no subscription/wake sections (### Parameters optional for a nullary call)", () => { for (const f of functions) { const source = read(f); - // plan.md §4 L112-L113: callables declare Parameters -> Returns, no - // Requires/Maintains; architecture.md §7.2 L290 (no Continuity on a function). + // Callables declare Parameters -> Returns, no Requires/Maintains, and no + // Continuity (a function is never woken). // A nullary function (e.g. ensure-skills, which reads the workspace) may // omit ### Parameters, but it always returns a value. expect(source, f).toMatch(/^### Returns\b/m); @@ -131,19 +132,19 @@ describe("examples corpus — functions declare Parameters -> Returns (plan.md }); }); -describe("examples corpus — responsibilities are mounted nodes (plan.md §3, delta.md §B1 inversion)", () => { +describe("examples corpus — responsibilities are mounted nodes", () => { const responsibilities = ALL.filter((f) => kindOf(f) === "responsibility"); it("there are exactly the seven re-authored responsibilities", () => { // One headline responsibility per non-trivial example (the spec's core - // inversion, delta.md §B1 L277 / §B7 L380-L381). + // inversion: the responsibility is the node, its helpers are called). expect(responsibilities.length).toBe(7); }); it("each gains both halves of the interface: ### Requires AND ### Maintains", () => { for (const f of responsibilities) { const source = read(f); - // plan.md §3 L99 / §4 L110: responsibility interface is Requires -> Maintains. + // The responsibility interface is Requires -> Maintains. expect(source, f).toMatch(/^### Requires\b/m); expect(source, f).toMatch(/^### Maintains\b/m); } @@ -152,7 +153,7 @@ describe("examples corpus — responsibilities are mounted nodes (plan.md §3, d it("each declares a ### Continuity wake-source (input/self/external)", () => { for (const f of responsibilities) { const source = read(f); - // architecture.md §7.2 L290-L291: Continuity is the intrinsic wake-source. + // Continuity is the intrinsic wake-source declaration. expect(source, f).toMatch(/^### Continuity\b/m); expect(read(f), f).toMatch(/input-driven|self-driven|external-driven/); } @@ -161,7 +162,7 @@ describe("examples corpus — responsibilities are mounted nodes (plan.md §3, d it("each declares an ### Execution that calls its helper functions (intra-node `call`)", () => { for (const f of responsibilities) { const source = read(f); - // plan.md §7 L150-L159: inside a node, composition is imperative `call`. + // Inside a node, composition is imperative `call`. expect(source, f).toMatch(/^### Execution\b/m); expect(source, f).toMatch(/\bcall\s+[a-z-]+/); } @@ -169,25 +170,25 @@ describe("examples corpus — responsibilities are mounted nodes (plan.md §3, d it("each ### Maintains carries a postcondition (the folded-in ### Criteria)", () => { for (const f of responsibilities) { - // world-model.md §2 L99-L100: Criteria fold into Maintains postconditions. + // Criteria fold into Maintains postconditions. expect(read(f), f).toMatch(/postcondition/i); } }); }); -describe("examples corpus — gateways are external-driven responsibilities (plan.md §3/§5)", () => { +describe("examples corpus — gateways are external-driven responsibilities", () => { const gateways = ALL.filter((f) => kindOf(f) === "gateway"); it("there is one gateway per event-driven example", () => { - // delta.md §B7 L373-L374: the gateway files gain `### Continuity: external-driven`. + // The gateway files declare an external-driven `### Continuity`. expect(gateways.length).toBeGreaterThan(0); }); it("each gateway declares explicit `### Continuity: external-driven`, no ### Requires, and a ### Maintains", () => { for (const f of gateways) { const source = read(f); - // delta.md §B1 L278 / architecture.md §7.1 L272: gateway = sugar for an - // external-driven responsibility; no Requires; maintains incoming truth. + // A gateway is sugar for an external-driven responsibility: no Requires, + // maintains the incoming truth. expect(source, f).toMatch(/^### Continuity\b/m); expect(read(f).replace(/\s+/g, " "), f).toMatch( /### Continuity\s*-?\s*external-driven/, @@ -209,9 +210,9 @@ describe("examples corpus — gateways are external-driven responsibilities (pla }); }); -describe("examples corpus — memory-fold (delta.md §B7 MEMORY-FOLD)", () => { +describe("examples corpus — memory-fold", () => { it("the pure `*-ledger` / `record-*` writer services were folded away, not left as functions", () => { - // delta.md §B7 L383-L384: ledger-writer services fold into the parent + // Ledger-writer services fold into the parent // responsibility's world-model; they are not separate nodes anymore. const retiredWriters = [ "customer-risk-radar/src/update-risk-ledger.prose.md", @@ -225,8 +226,8 @@ describe("examples corpus — memory-fold (delta.md §B7 MEMORY-FOLD)", () => { }); it("a responsibility that absorbed a ledger now keeps a durable `history`/register facet in its WM", () => { - // delta.md §B7 L385-L390: the ledger held decision history; that becomes a facet. - // delta.md Part G L548-L555: facets are declared as `#### ` named parts. + // The ledger held decision history; that becomes a facet, declared as a + // `#### ` named part (the named-parts rule in contract-markdown.md). const risk = read( join( examplesDir, @@ -238,9 +239,9 @@ describe("examples corpus — memory-fold (delta.md §B7 MEMORY-FOLD)", () => { }); }); -describe("examples corpus — the system orchestrators were deleted (plan.md §3)", () => { +describe("examples corpus — the system orchestrators were deleted", () => { it("no `*-system` orchestration file survives where one existed", () => { - // plan.md §3 L105: composition is `call` or subscription, never a system kind. + // Composition is `call` or subscription, never a system kind. const deletedSystems = [ "customer-risk-radar/src/risk-radar.prose.md", "research-inbox-triage/src/research-inbox-triage.prose.md", diff --git a/tests/open-prose/examples-corpus/facet-named-parts.test.ts b/tests/open-prose/examples-corpus/facet-named-parts.test.ts index 18c956de..6684ad61 100644 --- a/tests/open-prose/examples-corpus/facet-named-parts.test.ts +++ b/tests/open-prose/examples-corpus/facet-named-parts.test.ts @@ -1,20 +1,19 @@ // Conformance test for the FACET named-parts model across the examples corpus. // -// The facet-syntax decision is settled (delta.md Part G L548-L555: "a `####` -// sub-heading inside `### Maintains` declares a facet; ... One name = fingerprint -// unit + subscription symbol (`Requires.` <-> `Maintains.`) + -// world-model subtree (`published//...`)"). This test proves the public +// The facet syntax is the named-parts rule in contract-markdown.md: a `####` +// sub-heading inside `### Maintains` declares a facet, and one name is the +// fingerprint unit + subscription symbol (`Requires.` <-> `Maintains.`) +// + world-model subtree (`published//...`). This test proves the public // learning surface authors facets THAT way: // - the canonical competitor-activity-monitor declares its facets as // `#### funding` / `#### hiring` / `#### product-launches` named parts -// (architecture.md §3.2 worked example L182-L191); +// (the worked example in contract-markdown.md); // - each subscribed `####` part carries a structured material backing, the -// structured-backing rule (world-model.md §3 L177-L182; architecture.md §3.2 -// L144-L148); +// structured-backing rule; // - every migrated example replaced the old prose-bullet facet form // ("`X` facet (material): ...") with `####` parts; // - state/filesystem.md documents the `published//...` on-disk layout -// (delta.md Part G L592; world-model.md §3 "Declaring facets"). +// (one subtree per facet). // // It is a doc-conformance test in the same style as // tests/open-prose/examples-corpus/vendor-renewal-watch.test.ts: it reads the source @@ -66,6 +65,12 @@ const FACETED: { rel: string; facets: string[] }[] = [ rel: "competitor-activity/src/competitor-activity-monitor.prose.md", facets: ["funding", "hiring", "product-launches"], }, + // The monitor's upstream: a gateway whose three signal facets are exactly + // the symbols the monitor's three ### Requires needs resolve to. + { + rel: "competitor-activity/src/signal-feeds.prose.md", + facets: ["funding-signals", "hiring-signals", "launch-signals"], + }, { rel: "vendor-renewal-watch/src/vendor-renewals-prepared.prose.md", facets: ["recommendation", "history", "ownership"], @@ -78,6 +83,12 @@ const FACETED: { rel: string; facets: string[] }[] = [ rel: "research-inbox-triage/src/research-inbox-responsibility.prose.md", facets: ["report", "topics", "ignored"], }, + // The human-maintained registry the triage responsibility's active-questions + // and available-owners needs resolve to, one facet each. + { + rel: "research-inbox-triage/src/research-registry.prose.md", + facets: ["active-questions", "available-owners"], + }, { rel: "stargazer-outreach/src/high-intent-stargazer-outreach.prose.md", facets: ["qualification", "contact-history"], @@ -98,9 +109,35 @@ const FACETED: { rel: string; facets: string[] }[] = [ rel: "content-performance-loop/src/content-learning-cycle.prose.md", facets: ["brief", "actions", "history"], }, + // implementation-pipeline declares its lane facets and feed facets as #### + // parts inside ### Maintains, each with its own material boundary. + { + rel: "implementation-pipeline/src/implementation-work-plan.prose.md", + facets: [ + "lane:sdk-world-model", + "lane:sdk-runtime", + "lane:sdk-compile", + "lane:skill-contract", + "lane:examples-tests", + "lane:docs-signposts", + "diagnostics", + ], + }, + { + rel: "implementation-pipeline/src/construction-review.prose.md", + facets: ["accepted"], + }, + { + rel: "implementation-pipeline/src/planning-corpus.prose.md", + facets: ["docs", "repo", "config"], + }, + { + rel: "implementation-pipeline/src/foundation-builder.prose.md", + facets: ["shared-shapes"], + }, ]; -describe("canonical competitor-activity-monitor declares facets as #### named parts (delta.md Part G; architecture.md §3.2)", () => { +describe("canonical competitor-activity-monitor declares facets as #### named parts", () => { const rel = "competitor-activity/src/competitor-activity-monitor.prose.md"; it("is a mounted responsibility whose ### Maintains contains #### facet parts", () => { @@ -108,16 +145,16 @@ describe("canonical competitor-activity-monitor declares facets as #### named pa expect(source).toMatch(/kind:\s*responsibility/); const block = maintainsBlock(source); const parts = facetParts(block); - // architecture.md §3.2 L182-L191: the three named parts. + // The three named parts of the worked example. expect(parts).toEqual(["funding", "hiring", "product-launches"]); }); - it("each subscribed facet part has a structured MATERIAL backing (the structured-backing rule, world-model.md §3)", () => { + it("each subscribed facet part has a structured MATERIAL backing (the structured-backing rule)", () => { const block = maintainsBlock(read(rel)); const parts = block.split(/^#### /m).slice(1); // Each `#### ` body must state what is material (its structured // backing) so the subscribed token is computed over real structure, not - // re-rendered prose (world-model.md §3 L177-L182). + // re-rendered prose. for (const part of parts) { expect(part).toMatch(/[Mm]aterial:/); } @@ -125,7 +162,7 @@ describe("canonical competitor-activity-monitor declares facets as #### named pa it("names the funding / hiring / launch ### Requires inputs the facets join on", () => { const source = read(rel); - // Requires. <-> Maintains. (architecture.md §6.3; delta.md Part G L586-L587). + // Requires. <-> Maintains. is the subscription join. expect(source).toMatch(/^### Requires\b/m); expect(source).toMatch(/funding-signals/); expect(source).toMatch(/hiring-signals/); @@ -134,13 +171,13 @@ describe("canonical competitor-activity-monitor declares facets as #### named pa it("keeps shared un-facetted fields (name / last_corroborated) outside any part — atomic-only", () => { const block = maintainsBlock(read(rel)).replace(/\s+/g, " "); - // architecture.md §3.2 L195-L196: shared fields move only the atomic token. + // Shared fields move only the atomic token. expect(block).toMatch(/last_corroborated/); expect(block).toMatch(/@atomic|atomic token/); }); }); -describe("every faceted example uses #### named parts, not prose-bullet facets (delta.md Part G migration)", () => { +describe("every faceted example uses #### named parts, not prose-bullet facets", () => { it("declares each facet as a #### sub-heading under ### Maintains", () => { for (const { rel, facets } of FACETED) { const block = maintainsBlock(read(rel)); @@ -183,7 +220,7 @@ describe("every faceted example uses #### named parts, not prose-bullet facets ( }); }); -describe("state/filesystem.md documents the published//... layout (delta.md Part G L592; world-model.md §3)", () => { +describe("state/filesystem.md documents the published//... layout", () => { function fs(): string { return readFileSync(join(skillDir, "state/filesystem.md"), "utf8"); } diff --git a/tests/open-prose/examples-corpus/intelligent-react-examples-corpus.test.ts b/tests/open-prose/examples-corpus/intelligent-react-examples-corpus.test.ts index 4ee5c6cc..3beac82f 100644 --- a/tests/open-prose/examples-corpus/intelligent-react-examples-corpus.test.ts +++ b/tests/open-prose/examples-corpus/intelligent-react-examples-corpus.test.ts @@ -7,8 +7,8 @@ // that test asserts the LEGACY judge-era corpus shape: an EXACT // `responsibilities.length === 7` count and a universal `### Execution` + `call` // requirement. The Intelligent-React examples model larger DAGs with many -// pure-subscriber responsibilities (no helper `call`), and several use the -// `### Continuity: external-driven` colon form, so they need their own, +// pure-subscriber responsibilities (no helper `call`) and gateways that declare +// `### Continuity` with an `external-driven` bullet, so they need their own, // structurally-correct conformance assertions rather than being forced into the // legacy count + Execution shape. // @@ -40,6 +40,15 @@ const OWNED_EXAMPLES = [ // green, gated ledger-replay tests. "agent-observatory", "basic-unit-suite", + // competitor-activity is the canonical named-parts example: one + // external-driven signal gateway feeding an input- and self-driven monitor + // facet by facet. It has no helper functions and no `### Execution`, so it + // fits this shape rather than the legacy count + Execution shape. + "competitor-activity", + // The three inbound-email examples share this shape: one external-driven + // inbox gateway fanning out to input-driven (and, where a standing cadence + // applies, self-driven) responsibilities, no helper functions. + "feedback-pulse", "forme-fixpoint", "github-star-enricher", "implementation-pipeline", @@ -47,8 +56,10 @@ const OWNED_EXAMPLES = [ "masked-relay", "monorepo-ci", "oblique-weave", + "press-desk", "renewal-risk", "research-tree", + "support-inbox-router", "surprise-cost", "tamper-forge", ]; diff --git a/tests/open-prose/examples-corpus/vendor-renewal-watch.test.ts b/tests/open-prose/examples-corpus/vendor-renewal-watch.test.ts index 8b4508c6..023f2a2f 100644 --- a/tests/open-prose/examples-corpus/vendor-renewal-watch.test.ts +++ b/tests/open-prose/examples-corpus/vendor-renewal-watch.test.ts @@ -2,15 +2,14 @@ // skills/open-prose/examples/vendor-renewal-watch. // // This example is the reference end-to-end exercise of the mounted-responsibility -// model (delta.md Part B §B7 L392-L398: "adopt it ... as the S1-S5 canonical -// example, re-authored as a mounted `responsibility` with cross-node helper -// `function`s"). It must demonstrate, in one repo: -// - a responsibility maintaining a world-model (plan.md §3) -// - a fingerprint-driven skip (world-model.md §3, SHAPES §0) -// - a `function` call helper (plan.md §3/§4) -// - a `gateway` for external input (plan.md §3/§5) -// - facets routing propagation (world-model.md §3/§5) -// - a memory ledger of decision-history + watermark (delta.md §B7 L385-L390) +// model: the canonical example, re-authored as a mounted `responsibility` with +// helper `function`s. It must demonstrate, in one repo: +// - a responsibility maintaining a world-model +// - a fingerprint-driven skip +// - a `function` call helper +// - a `gateway` for external input +// - facets routing propagation +// - a memory ledger of decision-history + watermark // // It is a doc-conformance test in the same style as // tests/open-prose/forme/forme.test.ts: it reads the source `.prose.md` files @@ -42,7 +41,7 @@ function frontmatter(rel: string): string { return source.slice(0, end + 4); } -describe("vendor-renewal-watch — the retired vocabulary is gone (delta.md §B1/§B2/§B7)", () => { +describe("vendor-renewal-watch — the retired vocabulary is gone", () => { const files = [ "vendor-renewals-prepared.prose.md", "collect-renewal-signals.prose.md", @@ -54,7 +53,7 @@ describe("vendor-renewal-watch — the retired vocabulary is gone (delta.md §B1 it("declares no `service` or `system` kind anywhere", () => { for (const f of files) { const fm = frontmatter(f); - // delta.md §B1: service -> function, system -> deleted. + // service -> function, system -> deleted. expect(fm).not.toMatch(/kind:\s*service/); expect(fm).not.toMatch(/kind:\s*system/); } @@ -63,7 +62,7 @@ describe("vendor-renewal-watch — the retired vocabulary is gone (delta.md §B1 it("uses no `### Ensures`, `### Services`, `### Wiring`, `### Criteria`, `### Fulfillment` section headers", () => { for (const f of files) { const source = read(f); - // delta.md §B2: Ensures->Maintains; Criteria/Fulfillment folded; §B1 system gone. + // Ensures -> Maintains; Criteria/Fulfillment folded; system gone. expect(source).not.toMatch(/^### Ensures\b/m); expect(source).not.toMatch(/^### Services\b/m); expect(source).not.toMatch(/^### Wiring\b/m); @@ -74,52 +73,52 @@ describe("vendor-renewal-watch — the retired vocabulary is gone (delta.md §B1 it("retired the standalone `### Memory` ledger header (memory-fold into the world-model)", () => { for (const f of files) { - // delta.md §B7 L383-L384: ledger-writer services fold into the parent + // Ledger-writer services fold into the parent // responsibility's world-model, not a separate ledger. expect(read(f)).not.toMatch(/^### Memory\b/m); } }); }); -describe("vendor-renewal-watch — a responsibility maintaining a world-model (plan.md §3)", () => { +describe("vendor-renewal-watch — a responsibility maintaining a world-model", () => { const f = "vendor-renewals-prepared.prose.md"; it("is a mounted responsibility with ### Requires -> ### Maintains", () => { expect(frontmatter(f)).toMatch(/kind:\s*responsibility/); const source = read(f); - // plan.md §3: responsibility interface is Requires -> Maintains. + // The responsibility interface is Requires -> Maintains. expect(source).toMatch(/^### Requires\b/m); expect(source).toMatch(/^### Maintains\b/m); }); it("declares a vendor-keyed ledger as its maintained truth (world-model schema)", () => { const source = flat(f); - // world-model.md §2: ### Maintains is the WM schema (type/canon/facets/postconditions). + // ### Maintains is the world-model schema (type/canon/facets/postconditions). expect(source).toMatch(/vendor renewal ledger|vendor.+ledger/i); expect(source).toMatch(/keyed by `vendor_id`|map keyed by/i); }); it("reads its prior world-model BY REFERENCE in the render, not pre-stuffed", () => { const source = flat(f); - // architecture.md §5.2 / SHAPES §5: render reads by reference (location). + // A render reads by reference (location), never pre-stuffed. expect(source).toMatch(/by reference/i); expect(source).toMatch(/read_world_model\("self"\)/); }); it("self-polices ### Maintains postconditions before signing (no separate judge beat)", () => { const source = flat(f); - // world-model.md §2 L99-L100; delta.md §B2 (Criteria folds in, no judge beat). + // Criteria folds into Maintains postconditions; there is no judge beat. expect(source).toMatch(/postconditions?/i); expect(source).toMatch(/no separate judge beat|self-polic/i); }); }); -describe("vendor-renewal-watch — fingerprint-driven skip (world-model.md §3, SHAPES §0)", () => { +describe("vendor-renewal-watch — fingerprint-driven skip", () => { const f = "collect-renewal-signals.prose.md"; it("carries a watermark as IMMATERIAL state so re-deliveries do not move the fingerprint", () => { const source = flat(f); - // world-model.md §3 L94-L98: immaterial fields are the highest-leverage memo control. + // Immaterial fields are the highest-leverage memo control. expect(source).toMatch(/watermark/i); expect(source).toMatch(/[Ii]mmaterial/); expect(source).toMatch(/latest_signal_at/); @@ -127,20 +126,21 @@ describe("vendor-renewal-watch — fingerprint-driven skip (world-model.md §3, it("explains that an unmoved fingerprint makes the downstream write a `skipped` receipt", () => { const source = flat(f); - // SHAPES §3 L78-L79 / §4: unmoved memo key => skipped receipt, spawns nothing. + // An unmoved memo key => skipped receipt, spawns nothing. expect(source).toMatch(/skipped/i); expect(source).toMatch(/spawns nothing|stops here|never reach/i); expect(source).toMatch(/cost scales with surprise/i); }); }); -describe("vendor-renewal-watch — a `function` call helper (plan.md §3/§4)", () => { +describe("vendor-renewal-watch — a `function` call helper", () => { const f = "score-vendor-renewal.prose.md"; it("is a `function` with ### Parameters -> ### Returns, not Requires/Maintains", () => { expect(frontmatter(f)).toMatch(/kind:\s*function/); const source = read(f); - // plan.md §4: callables declare Parameters -> Returns; de-overloads Requires/Maintains. + // Callables declare Parameters -> Returns; Requires/Maintains are not + // overloaded for calls. expect(source).toMatch(/^### Parameters\b/m); expect(source).toMatch(/^### Returns\b/m); expect(source).not.toMatch(/^### Maintains\b/m); @@ -149,7 +149,7 @@ describe("vendor-renewal-watch — a `function` call helper (plan.md §3/§4)", it("is stateless — no world-model — and the parent calls it via ProseScript `call`", () => { const fnSource = flat(f); - // plan.md §3: function is stateless, ephemeral; no world-model. + // A function is stateless, ephemeral; no world-model. expect(fnSource).toMatch(/stateless/i); // The headline responsibility invokes it imperatively. const parent = flat("vendor-renewals-prepared.prose.md"); @@ -157,14 +157,14 @@ describe("vendor-renewal-watch — a `function` call helper (plan.md §3/§4)", }); }); -describe("vendor-renewal-watch — a `gateway` for external input (plan.md §3/§5)", () => { +describe("vendor-renewal-watch — a `gateway` for external input", () => { const f = "renewal-review-events.prose.md"; it("is a gateway with explicit ### Continuity: external-driven and no ### Requires", () => { expect(frontmatter(f)).toMatch(/kind:\s*gateway/); const source = read(f); - // delta.md §B1: gateway gains explicit `### Continuity: external-driven`; - // plan.md §3: gateway has no ### Requires. + // A gateway declares an explicit external-driven `### Continuity` and has + // no ### Requires. expect(source).toMatch(/^### Continuity\b/m); expect(flat(f)).toMatch(/### Continuity external-driven/); expect(source).not.toMatch(/^### Requires\b/m); @@ -172,19 +172,19 @@ describe("vendor-renewal-watch — a `gateway` for external input (plan.md §3/ it("maintains the incoming-event truth that the collector subscribes to", () => { const source = flat(f); - // plan.md §3: gateway maintains the latest incoming truth. + // A gateway maintains the latest incoming truth. expect(source).toMatch(/^.*### Maintains/s); expect(source).toMatch(/renewal_events/); - // plan.md §5: external-driven nodes are the entry points. + // External-driven nodes are the entry points. expect(source).toMatch(/entry point/i); }); }); -describe("vendor-renewal-watch — facets routing propagation (world-model.md §3/§5)", () => { +describe("vendor-renewal-watch — facets routing propagation", () => { it("the assessor declares recommendation / history / ownership facets as #### named parts", () => { const source = read("vendor-renewals-prepared.prose.md"); - // world-model.md §3 L151-L157: facets make propagation finer-grained. - // delta.md Part G L548-L555: a `#### ` sub-heading IS a facet. + // Facets make propagation finer-grained; a `#### ` sub-heading IS a + // facet (the named-parts rule in contract-markdown.md). expect(source).toMatch(/[Ff]acets/); expect(source).toMatch(/^#### recommendation\b/m); expect(source).toMatch(/^#### history\b/m); @@ -193,7 +193,7 @@ describe("vendor-renewal-watch — facets routing propagation (world-model.md § it("the brief writer subscribes to the `recommendation` facet ONLY (selector, not atomic)", () => { const source = flat("prepare-renewal-brief.prose.md"); - // world-model.md §5 L217-L219: B depends on a NAMED facet of A's Maintains. + // B depends on a NAMED facet of A's Maintains. expect(source).toMatch(/facet `recommendation`|`recommendation` facet/); expect(source).toMatch( /never wakes? on `history`|not.+`history`|not on the decision-history/i, @@ -201,23 +201,23 @@ describe("vendor-renewal-watch — facets routing propagation (world-model.md § }); }); -describe("vendor-renewal-watch — memory ledger: decision-history + watermark (delta.md §B7)", () => { +describe("vendor-renewal-watch — memory ledger: decision-history + watermark", () => { it("the assessor's truth holds an append-only decision_history", () => { const source = flat("vendor-renewals-prepared.prose.md"); - // delta.md §B7 L385-L390: the ledger holds decision history, not just latest truth. + // The ledger holds decision history, not just latest truth. expect(source).toMatch(/decision_history/); expect(source).toMatch(/append-only/i); }); it("the collector's truth holds the watermark (transient watermark state in the WM)", () => { const source = flat("collect-renewal-signals.prose.md"); - // delta.md §B7 L385-L390 + L383-L384: watermark state lives in the WM, not a ledger. + // Watermark state lives in the world-model, not a separate ledger. expect(source).toMatch(/watermark/i); expect(source).toMatch(/latest_signal_at/); }); }); -describe("vendor-renewal-watch — README frames the canonical eval (delta.md §B7)", () => { +describe("vendor-renewal-watch — README frames the canonical eval", () => { function readme(): string { return readFileSync(join(exampleDir, "README.md"), "utf8").replace( /\s+/g, @@ -234,7 +234,7 @@ describe("vendor-renewal-watch — README frames the canonical eval (delta.md § expect(r).toMatch( /decision history.*watermark|watermark.*decision history/i, ); - // delta.md §B4 / architecture.md §2: compile (intelligent) / run (dumb), + // compile (intelligent) / run (dumb), // joined by promoting the compiled IR to the active manifest. expect(r).toMatch(/intelligent phase|prose compile/i); expect(r).toMatch(/cp dist\/manifest\.next\.json dist\/manifest\.active\.json/); diff --git a/tests/open-prose/forme/forme.test.ts b/tests/open-prose/forme/forme.test.ts index 9a617567..bbb1d3d5 100644 --- a/tests/open-prose/forme/forme.test.ts +++ b/tests/open-prose/forme/forme.test.ts @@ -4,8 +4,8 @@ // in BOTH scope (intra-`system` service wiring -> the responsibility DAG) and // layer (a SKILL-phase manifest compiler -> an SDK compile-phase render emitting // the topology world-model). This asserts the doc embodies that end-state -// (delta.md Part B §B3/§B6 + Part F; plan.md §5; architecture.md §2/§3.1/§6.3; -// world-model.md §1/§3). It is a doc-conformance test in the same style as +// (compile-phase wiring, the diamond rule, diagnostics over guesses). It is a +// doc-conformance test in the same style as // tests/open-prose/contract-markdown/contract-markdown.test.ts — it reads the // source doc and asserts on its content; no runtime. // @@ -40,14 +40,14 @@ function frontmatter(): string { describe("forme.md — layer relocation: SKILL-phase compiler -> compile-phase render", () => { it("declares Forme a compile-phase render, not a manifest compiler", () => { const source = doc(); - // architecture.md §2 L83-L91 / §3.1 L111; delta.md §B3 L304-L309. + // Forme is a compile-phase render; the manifest compiler it replaced is retired. expect(source).toMatch(/compile-phase render/i); expect(source).toMatch(/intelligent at compile.*dumb at run|compile.+intelligent.+run.+dumb/is); }); it("splits the run into a compile (intelligent) and run (dumb) phase", () => { const source = doc(); - // architecture.md §2 L78-L97. + // Two phases: compile fires on contract-set change, run fires on every wake. expect(source).toMatch(/Compile.*fires on contract-set change/i); expect(source).toMatch(/Run.*fires on every wake/i); expect(source).toMatch(/reconciler reads `topology\.edges`|reads `topology\.edges`/); @@ -55,7 +55,7 @@ describe("forme.md — layer relocation: SKILL-phase compiler -> compile-phase r it("frames Forme as a render with a contract, world-model, and receipt (auditable)", () => { const source = doc(); - // architecture.md §2 L90: each compile step is itself a render -> auditable. + // Each compile step is itself a render -> auditable. expect(source).toMatch(/Forme is one of them|Forme is a render/i); expect(source).toMatch(/signs a receipt/); expect(source).toMatch(/auditable/i); @@ -63,8 +63,8 @@ describe("forme.md — layer relocation: SKILL-phase compiler -> compile-phase r it("breaks the bootstrap regress via a wiring-exempt registry read", () => { const source = doc(); - // architecture.md §3.1 L128-L132: Requires = all declared contracts, exempt - // from Forme's own wiring. + // Forme's own Requires is the set of all declared contracts, exempt from + // Forme's own wiring. expect(source).toMatch(/set of all declared contracts/i); expect(source).toMatch(/exempt from Forme's own wiring/i); expect(source).toMatch(/bootstrap regress/i); @@ -74,7 +74,7 @@ describe("forme.md — layer relocation: SKILL-phase compiler -> compile-phase r describe("forme.md — scope relocation: intra-system wiring -> the responsibility DAG", () => { it("declares Forme wires the DAG only, not agents inside a node", () => { const source = doc(); - // plan.md §5 L141; architecture.md §3.1. + // Forme wires the responsibility DAG; intra-node composition is imperative call. expect(source).toMatch(/Forme wires the DAG only/i); expect(source).toMatch(/no intra-node autowiring/i); expect(source).toMatch(/imperative.+`call`|`call`.+imperative/is); @@ -82,15 +82,22 @@ describe("forme.md — scope relocation: intra-system wiring -> the responsibili it("matches ### Requires facet-contract to ### Maintains facet semantically", () => { const source = doc(); - // architecture.md §3.1 L113-L114; plan.md §5 L137. + // Matching is semantic, never string-matching. expect(source).toMatch(/### Requires.+### Maintains|### Maintains.+### Requires/s); expect(source).toMatch(/semantically/i); expect(source).toMatch(/not by string|never by string|string-match/i); }); + it("matches a placeholder facet-need against the family it names, bound per mount by the harness", () => { + const source = doc(); + expect(source).toMatch(/placeholder.+matches any member of the family it names/i); + expect(source).toMatch(/harness binds the member at mount time/i); + expect(source).toMatch(/never enumerates entities it cannot know at compile time/i); + }); + it("honors deliberate fan-in as the diamond rule (one slot per producer)", () => { const source = doc(); - // plan.md §5 L137; architecture.md §3.1 L122; world-model.md §3 L148-L150. + // Fan-in is one slot per producer in the subscriber's input tuple. expect(source).toMatch(/diamond rule/i); expect(source).toMatch(/once per distinct input-fingerprint tuple/i); expect(source).toMatch(/distinct slot|slot per producer/i); @@ -98,7 +105,7 @@ describe("forme.md — scope relocation: intra-system wiring -> the responsibili it("surfaces unsatisfied and ambiguous matches as diagnostics, never a silent guess", () => { const source = doc(); - // architecture.md §3.1 L120-L123; plan.md §5 L137. + // Unsatisfied and ambiguous matches are diagnostics, never guesses. expect(source).toMatch(/never.+guess|guess.+never/is); expect(source).toMatch(/[Uu]nsatisfied/); expect(source).toMatch(/[Aa]mbiguous/); @@ -109,7 +116,7 @@ describe("forme.md — scope relocation: intra-system wiring -> the responsibili describe("forme.md — the topology world-model (Forme's output)", () => { it("emits the topology world-model with nodes/edges/entry_points/acyclic", () => { const source = doc(); - // architecture.md §6.3 L256-L261; SHAPES.md §6. + // The topology block of the compile-phase IR (compiler/ir-v0.md). expect(source).toMatch(/topology world-model/i); expect(source).toContain("nodes"); expect(source).toContain("edges"); @@ -119,7 +126,7 @@ describe("forme.md — the topology world-model (Forme's output)", () => { it("draws each edge as subscriber.Requires. -> producer.Maintains.", () => { const source = doc(); - // architecture.md §6.3 L258; SHAPES.md §6 (TopologyEdge). + // An edge is subscriber.Requires. -> producer.Maintains.. expect(source).toMatch(/subscriber/); expect(source).toMatch(/producer/); expect(source).toMatch(/Requires.+Maintains|Maintains.+Requires/s); @@ -127,13 +134,14 @@ describe("forme.md — the topology world-model (Forme's output)", () => { it("uses the atomic facet for a facet-less producer", () => { const source = doc(); - // SHAPES.md §1 (ATOMIC_FACET) / §6; architecture.md §3.1 L113. + // A producer with no #### parts exposes the reserved @atomic facet. expect(source).toMatch(/@atomic|atomic.+facet|facet-less/i); }); it("registers external-driven nodes (gateways) as entry points read from ### Continuity", () => { const source = doc(); - // plan.md §5 L133-L135; architecture.md §3.1 L121. + // Entry points come from an external-driven ### Continuity, never an + // inferred trigger. expect(source).toMatch(/entry point/i); expect(source).toMatch(/external-driven/); expect(source).toMatch(/### Continuity/); @@ -142,7 +150,7 @@ describe("forme.md — the topology world-model (Forme's output)", () => { it("only responsibility and gateway kinds become topology nodes (not function)", () => { const source = doc(); - // plan.md §3 L92-L105; architecture.md §7.1. + // Only mounted kinds become nodes; a function is called, never mounted. expect(source).toMatch(/`function`.+never.+node|never.+topology node/i); expect(source).toMatch(/responsibility.+gateway|gateway.+responsibility/i); }); @@ -151,7 +159,7 @@ describe("forme.md — the topology world-model (Forme's output)", () => { describe("forme.md — acyclicity as a postcondition; feedback is time, not an edge", () => { it("makes acyclicity a postcondition on Forme's own ### Maintains", () => { const source = doc(); - // plan.md §5 L139; architecture.md §3.1 L116/L132-L133. + // Acyclicity is a postcondition on Forme's own ### Maintains. expect(source).toMatch(/postcondition/i); expect(source).toMatch(/acyclic/i); expect(source).toMatch(/### Maintains/); @@ -159,7 +167,7 @@ describe("forme.md — acyclicity as a postcondition; feedback is time, not an e it("distinguishes a graph back-edge from self-driven feedback (loops live in time)", () => { const source = doc(); - // architecture.md §3.1 L124-L127; plan.md §5 L139. + // Self-driven feedback is time, not a graph back-edge. expect(source).toMatch(/[Ll]oops live in time, not in edges/); expect(source).toMatch(/self-driven `### Continuity`/); expect(source).toMatch(/never subscribes to its own facet|not.+graph cycle/i); @@ -167,24 +175,24 @@ describe("forme.md — acyclicity as a postcondition; feedback is time, not an e it("reuses the reconciler's deterministic cycle detector for the check", () => { const source = doc(); - // delta.md §A4 L202: detectReceiptCycles moves to Forme as the acyclicity + // The reconciler's cycle detector is reused as the acyclicity // postcondition. expect(source).toMatch(/cycle det/i); expect(source).toMatch(/reused|reuse/i); }); }); -describe("forme.md — what was retired (delta.md §B3 / Part F)", () => { +describe("forme.md — what was retired", () => { it("retires the system kind and gives the composition replacement", () => { const source = doc(); - // plan.md §3 L105; delta.md §B1. + // There is no system kind; composition is call or subscription. expect(source).toMatch(/no `system` kind/); expect(source).toMatch(/never a third/i); }); it("retires ### Wiring and the Level-2/Level-3 author-control levels", () => { const source = doc(); - // delta.md §B3 L310-L312. + // The Level-2/Level-3 author-control levels and ### Wiring are retired. expect(source).toMatch(/### Wiring/); expect(source).toMatch(/Level-2|Level 2/); expect(source).toMatch(/Level-3|Level 3/); @@ -193,14 +201,14 @@ describe("forme.md — what was retired (delta.md §B3 / Part F)", () => { it("retires the per-system manifest in favor of the topology world-model", () => { const source = doc(); - // delta.md §B3 L312; architecture.md §6.3. + // The topology world-model replaces the per-system manifest. expect(source).toMatch(/manifest/i); expect(source).toMatch(/per-system manifest.+retired|retired.+manifest|replaces it/i); }); it("does NOT teach the old three-author-control / system-wiring algorithm as live", () => { const source = doc(); - // delta.md Part F: spec wins; the per-system manifest compiler is retired. + // The per-system manifest compiler is retired. // The old doc emitted manifest.next.json / forme.manifest.json as the live // output; the rewrite must not present those as the current output. expect(source).not.toMatch(/Emit the compiled Forme manifest as structured JSON/); @@ -210,7 +218,7 @@ describe("forme.md — what was retired (delta.md §B3 / Part F)", () => { it("draws the clean boundary: author declares need + wake-source, Forme infers wiring", () => { const source = doc(); - // plan.md §5 L135: Forme infers the wiring; the author declares the wake-source. + // Forme infers the wiring; the author declares the wake-source. expect(source).toMatch(/Forme infers the.+wiring|infers? the.+wiring/i); expect(source).toMatch(/declares? the.+wake-source|wake-source.+author|author.+wake-source/i); }); @@ -219,14 +227,14 @@ describe("forme.md — what was retired (delta.md §B3 / Part F)", () => { describe("forme.md — frontmatter + cross-doc seam", () => { it("declares a topology-wiring role and points at the compile-phase IR seam", () => { const fm = frontmatter(); - // delta.md §B6: forme.md is consistent with compiler/ir-v0.md (the IR seam). + // forme.md is consistent with compiler/ir-v0.md (the IR seam). expect(fm).toMatch(/role:\s*topology-wiring/); expect(fm).toMatch(/compiler\/ir-v0\.md/); }); it("places the topology inside the compile-phase IR alongside canonicalizers + validators", () => { const source = doc(); - // SHAPES.md §6 (CompilePhaseIR.topology); architecture.md §2 L86-L91. + // The topology sits inside the compile-phase IR (compiler/ir-v0.md). expect(source).toMatch(/compile-phase IR/i); expect(source).toMatch(/canonicalizer/i); expect(source).toMatch(/postcondition (validator|compiler)|validator/i); diff --git a/tests/open-prose/primitives/session.test.ts b/tests/open-prose/primitives/session.test.ts index bf0a8a12..e1c0164c 100644 --- a/tests/open-prose/primitives/session.test.ts +++ b/tests/open-prose/primitives/session.test.ts @@ -1,5 +1,5 @@ // Conformance test for the RESHAPED SKILL doc, primitives/session.md — the -// render's harness contract (architecture.md §7.3). +// render's harness contract. // // session.md is the existing render-harness contract reshaped to the end-state: // a render reads its inputs + prior world-model BY REFERENCE, leaves its @@ -9,9 +9,7 @@ // standalone). It signals `rendered` or `failed`; `skipped` is never the render's // signal. Language-layer sovereignty: a render only knows its own node. // -// Justification: architecture.md §1 (L26-L54), §5.2 (L206-L219), §7.3 (L301-L305), -// §7.4 (L307-L312); world-model.md §3 (L162-L177); SHAPES.md §0/§4; delta.md §B6 -// L347. Doc-conformance style matching tests/open-prose/forme/forme.test.ts. +// Doc-conformance style matching tests/open-prose/forme/forme.test.ts. // // RUN from the repo root: // cd /Users/sl/code/prose && npx vitest run tests/open-prose/primitives @@ -30,14 +28,14 @@ function doc(): string { describe("session.md — the render atom and language-layer sovereignty", () => { it("frames the session as a render: (contract, evidence, prior WM) -> (new WM, receipt)", () => { const source = doc(); - // architecture.md §1 L26-L27. + // The render atom signature. expect(source).toMatch(/\bcontract,?\s*evidence,?\s*prior world-model\b/i); expect(source).toMatch(/new world-model,?\s*receipt/i); }); it("keeps language-layer sovereignty: a render only knows its own node", () => { const source = doc(); - // plan.md "Language sovereignty"; architecture.md §1 standalone vs mounted. + // Language sovereignty: standalone vs mounted. expect(source).toMatch(/only know about your own/i); expect(source).toMatch(/standalone/i); expect(source).toMatch(/mounting is additive|mounting adds/i); @@ -47,7 +45,7 @@ describe("session.md — the render atom and language-layer sovereignty", () => describe("session.md — read by reference (evidence + prior world-model)", () => { it("reads inputs by reference, not inlined, via the waking receipt + semantic_diff", () => { const source = doc(); - // architecture.md §1 L44-L48, §8 evidence-by-reference. + // Evidence arrives by reference. expect(source).toMatch(/by reference/i); expect(source).toMatch(/semantic_diff/); expect(source).toMatch(/never (a wake signal|the reason you commit)/i); @@ -55,7 +53,7 @@ describe("session.md — read by reference (evidence + prior world-model)", () = it("reads the prior world-model by reference and treats it as continuity", () => { const source = doc(); - // architecture.md §1 L46-L48; world-model.md §9.4 (memory folded into WM). + // Memory is folded into the world-model. expect(source).toMatch(/prior world-model/i); expect(source).toMatch(/never pre-stuffed into context/i); // the single world-model subsumes the old per-agent memory ledger. @@ -64,7 +62,7 @@ describe("session.md — read by reference (evidence + prior world-model)", () = it("pins a content-addressed snapshot to avoid torn reads", () => { const source = doc(); - // architecture.md §8 cross-node read isolation. + // Cross-node read isolation. expect(source).toMatch(/pinned|snapshot/i); expect(source).toMatch(/torn read/i); }); @@ -73,14 +71,14 @@ describe("session.md — read by reference (evidence + prior world-model)", () = describe("session.md — workspace is never fingerprinted; world-model is canonical", () => { it("declares the workspace private scratch, never fingerprinted, never subscribed", () => { const source = doc(); - // architecture.md §5.2 L217-L219; SHAPES.md §0 invariant 3. + // Workspace scratch is never fingerprinted. expect(source).toMatch(/workspace[^.]*never fingerprinted/i); expect(source).toMatch(/never subscribed/i); }); it("commits the structured truth to the canonical world-model artifact", () => { const source = doc(); - // architecture.md §5.2 L206-L219; world-model.md §3 structured-backing rule. + // The structured-backing rule. expect(source).toMatch(/canonical world-model artifact/i); expect(source).toMatch(/fingerprint the structured truth/i); expect(source).toMatch(/render prose from it|derived projection/i); @@ -90,7 +88,7 @@ describe("session.md — workspace is never fingerprinted; world-model is canoni describe("session.md — postconditions, not a judge", () => { it("teaches deterministic verify-on-commit and render-attested postconditions", () => { const source = doc(); - // architecture.md §3.3 L154-L160. + // Commit-gating is validators plus self-attestation, not a judge. expect(source).toMatch(/postcondition/i); expect(source).toMatch(/attest/i); expect(source).toMatch(/no separate judge|There is no separate judge/i); @@ -100,7 +98,7 @@ describe("session.md — postconditions, not a judge", () => { describe("session.md — the receipt and the rendered/failed signal", () => { it("describes signing a receipt with fingerprints by applying the canonicalizer locally", () => { const source = doc(); - // architecture.md §6.1; world-model.md §3 phase-2 render signs; SHAPES.md §4. + // The render signs its own receipt. expect(source).toMatch(/\breceipt\b/i); expect(source).toMatch(/fingerprints/i); expect(source).toMatch(/compiled canonicalizer.*locally|applying.*canonicalizer/i); @@ -109,7 +107,7 @@ describe("session.md — the receipt and the rendered/failed signal", () => { it("signals rendered or failed; skipped is never the render's signal", () => { const source = doc(); - // architecture.md §1 L52-L54. + // rendered / failed are the render's signals. expect(source).toMatch(/\brendered\b/); expect(source).toMatch(/\bfailed\b/); expect(source).toMatch(/`?skipped`? is \*{0,2}never\*{0,2} your signal/i); @@ -118,7 +116,7 @@ describe("session.md — the receipt and the rendered/failed signal", () => { it("returns references not values", () => { const source = doc(); - // architecture.md §5.2 read-by-reference; the harness tracks pointers. + // Read-by-reference; the harness tracks pointers. expect(source).toMatch(/references,? not values|references, not values/i); }); }); @@ -126,7 +124,7 @@ describe("session.md — the receipt and the rendered/failed signal", () => { describe("session.md — retired vocabulary is gone", () => { it("does not teach the retired bindings/ensures/judge framing as live", () => { const source = doc(); - // delta.md Part F + §B2: ### Ensures -> ### Maintains; bindings -> world-model. + // ### Ensures -> ### Maintains; bindings -> world-model. expect(source).not.toMatch(/copy-on-return/i); expect(source).not.toMatch(/### Ensures/); expect(source).not.toMatch(/\bverdict\b/i); diff --git a/tests/open-prose/responsibility-runtime/responsibility-runtime.test.ts b/tests/open-prose/responsibility-runtime/responsibility-runtime.test.ts index 7593b45f..ec66b21d 100644 --- a/tests/open-prose/responsibility-runtime/responsibility-runtime.test.ts +++ b/tests/open-prose/responsibility-runtime/responsibility-runtime.test.ts @@ -9,9 +9,7 @@ // fulfillment activation, and no reference to the deleted // runtime/judge-responsibility.prose.md. // -// Justification: delta.md Part B §B4 (L313-L320) + §B6 (L341) + Part F; plan.md -// §2/§4; architecture.md §2 (L78-L97), §4.1 (L166-L178), §4.2 (L180-L191); -// world-model.md §3 (L138-L143). Doc-conformance style matching the sibling +// Doc-conformance style matching the sibling // tests/open-prose/forme/forme.test.ts — reads the doc and asserts on content. // // RUN from the repo root: @@ -31,14 +29,14 @@ function doc(): string { describe("responsibility-runtime.md — the judge loop is retired", () => { it("declares no judge in the wake or commit decision", () => { const source = doc(); - // world-model.md §3 L138-L143: "An LLM never judges 'did this change'". + // An LLM never judges "did this change". expect(source).toMatch(/judge in the wake or commit decision/i); expect(source).toMatch(/reconciler[^.]*compar(es|ing) fingerprints/i); }); it("retires the status enum, pressure, and fulfillment activation by name", () => { const source = doc(); - // delta.md §B4 L316-L317: status enum + pressure + fulfillment retired. + // Status enum + pressure + fulfillment retired. expect(source).toMatch(/no status enum/i); expect(source).toMatch(/up\/drifting\/down\/blocked.*retired|retired.*up\/drifting\/down\/blocked/i); expect(source).toMatch(/no pressure/i); @@ -47,7 +45,7 @@ describe("responsibility-runtime.md — the judge loop is retired", () => { it("does not reference the deleted judge-responsibility prose service", () => { const source = doc(); - // delta.md §B4 L320 / §B6 L364: runtime/judge-responsibility.prose.md DELETED. + // runtime/judge-responsibility.prose.md is DELETED. expect(source).not.toMatch(/judge-responsibility/i); expect(source).not.toMatch(/runtime\/judge/i); }); @@ -56,14 +54,14 @@ describe("responsibility-runtime.md — the judge loop is retired", () => { describe("responsibility-runtime.md — the compile (intelligent) / run (dumb) split", () => { it("frames compile as the only intelligent phase", () => { const source = doc(); - // architecture.md §2 L80-L92; delta.md §B4 L318-L320. + // Compile is the only intelligent phase. expect(source).toMatch(/compile[^.]*only special intelligent phase/i); expect(source).toMatch(/each compile step is itself a render/i); }); it("names the three compile artifacts: topology, canonicalizers, validators", () => { const source = doc(); - // architecture.md §3.1/§3.2/§3.3; delta.md §B6 L338 (IR rewrite). + // The three compile artifacts of compiler/ir-v0.md. expect(source).toMatch(/topology world-model/i); expect(source).toMatch(/canonicalizer/i); expect(source).toMatch(/postcondition validator/i); @@ -75,7 +73,7 @@ describe("responsibility-runtime.md — the compile (intelligent) / run (dumb) s it("describes the run phase as a dumb reconciler with memo + propagation", () => { const source = doc(); - // architecture.md §4.1 L166-L178; world-model.md §8. + // Memo + single-flight + propagate-on-move. expect(source).toMatch(/\(contract-?fingerprint, input-?fingerprints\)/i); expect(source).toMatch(/single-flight/i); expect(source).toMatch(/only `rendered`-?with-?a-?moved-?fingerprint propagates/i); @@ -85,7 +83,7 @@ describe("responsibility-runtime.md — the compile (intelligent) / run (dumb) s describe("responsibility-runtime.md — three wake sources + freshness as state vs policy", () => { it("names the three wake sources", () => { const source = doc(); - // architecture.md §4.2 L180-L184: input / self / external. + // input / self / external. expect(source).toMatch(/input-driven/i); expect(source).toMatch(/self-driven/i); expect(source).toMatch(/external-driven/i); @@ -93,7 +91,7 @@ describe("responsibility-runtime.md — three wake sources + freshness as state it("splits freshness state (world-model) from freshness policy (Continuity)", () => { const source = doc(); - // architecture.md §4.2 L185-L191; world-model.md §6. + // Freshness state vs freshness policy. expect(source).toMatch(/valid_until/i); expect(source).toMatch(/freshness.*state.*world-model|state.*lives.*in the world-model/i); expect(source).toMatch(/freshness.*policy.*Continuity|cadence.*Continuity/i); @@ -103,7 +101,7 @@ describe("responsibility-runtime.md — three wake sources + freshness as state describe("responsibility-runtime.md — command surface survives, reframed", () => { it("keeps compile/serve/run/status mapped onto compile and run phases", () => { const source = doc(); - // delta.md §B5 L327-L329: the command surface survives reframed. + // The command surface survives, reframed. expect(source).toMatch(/prose compile/); expect(source).toMatch(/prose serve/); expect(source).toMatch(/prose run/); diff --git a/tests/open-prose/skill-meta/skill-meta.test.ts b/tests/open-prose/skill-meta/skill-meta.test.ts index 7d50616f..55a3e4ee 100644 --- a/tests/open-prose/skill-meta/skill-meta.test.ts +++ b/tests/open-prose/skill-meta/skill-meta.test.ts @@ -3,7 +3,7 @@ // prosescript.md, deps.md, agent-onboarding.md). // // This asserts the docs embody the Intelligent React end-state versioning and -// vocabulary (delta.md Part B §B5/§B6, Part C §C3/§C5/§C7; plan.md §3-§7). It is +// vocabulary (the upgrade record in changelog.md names every move). It is // a doc-conformance test in the style of tests/open-prose/contract-markdown — // it reads the source docs and asserts on their content, no runtime. import { readFileSync } from "node:fs"; @@ -89,18 +89,20 @@ describe("skill-meta Markdown helpers", () => { }); }); -describe("SKILL.md frontmatter — versioning (delta.md §C7)", () => { +describe("SKILL.md frontmatter — versioning", () => { const fm = frontmatter(read("SKILL.md")); - it("pins version to 0.17.0", () => { - // 0.15.0 was the Intelligent React overhaul (delta.md Part C §C7 L465); + it("pins version to 0.18.0", () => { + // 0.15.0 was the Intelligent React overhaul; // 0.16.0 removes the harness product surface from the skill. // 0.17.0 introduces guided init/compose and the Compose std package. - expect(fm).toMatch(/^version:\s*0\.17\.0\s*$/m); + // 0.18.0 makes id: optional, documents facet families, and brings the + // example corpus into conformance with the compiler. + expect(fm).toMatch(/^version:\s*0\.18\.0\s*$/m); }); it("bumps runtime_contract to 2", () => { - // delta.md §C7 L465-466: runtime_contract: 1 -> 2 gates the re-cleave; + // runtime_contract: 1 -> 2 gates the re-cleave; // prose upgrade keys its applicability off it. expect(fm).toMatch(/^runtime_contract:\s*2\s*$/m); }); @@ -109,15 +111,17 @@ describe("SKILL.md frontmatter — versioning (delta.md §C7)", () => { expect(fm).not.toMatch(/^runtime_contract:\s*1\s*$/m); expect(fm).not.toMatch(/^version:\s*0\.14\.0\s*$/m); expect(fm).not.toMatch(/^version:\s*0\.15\.0\s*$/m); + expect(fm).not.toMatch(/^version:\s*0\.17\.0\s*$/m); }); }); -describe("changelog.md — the upgrade mechanism (delta.md Part C)", () => { +describe("changelog.md — the upgrade mechanism", () => { const doc = read("changelog.md"); const f = flat(doc); it("records the v0.15.0 overhaul entry with the runtime_contract bump", () => { - // delta.md §B6 changelog: EXTEND, bump runtime_contract (§C7). + // The changelog is extended, never rewritten; the entry records the + // runtime_contract bump. expect(f).toMatch(/`v0\.15\.0`/); expect(f).toMatch(/runtime_contract: 1 . 2/); // arrow may be unicode/ascii }); @@ -138,50 +142,50 @@ describe("changelog.md — the upgrade mechanism (delta.md Part C)", () => { }); it("names the retired judge loop in the overhaul entry", () => { - // delta.md §B4 / Part F: judge -> verdict -> pressure -> fulfillment retired. + // The judge -> verdict -> pressure -> fulfillment loop is retired. expect(f).toMatch(/judge .*?(retired|is retired)/i); expect(f).toMatch(/deterministic reconciler/i); expect(f).toMatch(/no LLM in the wake\/commit decision|no .* LLM/i); }); it("Migration Map: kind renames (service->function, system removed)", () => { - // delta.md §C3 L424-431 table. + // The Migration Map table records the kind renames. expect(doc).toContain("kind: service"); expect(doc).toContain("kind: function"); expect(f).toMatch(/`kind: system`.*?\*\(removed\)\*/); }); it("Migration Map: section renames (Ensures->Maintains, Memory removed)", () => { - // delta.md §C3 / §B2. + // The Migration Map table records the section renames. expect(f).toMatch(/`### Ensures`\s*\|\s*`### Maintains`/); expect(f).toMatch(/`### Memory`\s*\|\s*\*\(removed\)\*/); }); it("Migration Map: Criteria folds into Maintains postconditions", () => { - // plan.md §4 L115: Criteria deleted, folds into Maintains postconditions. + // Criteria is deleted; it folds into Maintains postconditions. expect(f).toMatch(/`### Criteria`.*?postcondition/i); }); it("Migration Map: gateway gains explicit external-driven Continuity", () => { - // delta.md §B1 / §C3. + // A gateway is an external-driven responsibility; the map says so. expect(doc).toContain("### Continuity: external-driven"); }); it("surfaces system/Wiring as a manual-review diagnostic, not an auto-guess", () => { - // delta.md §C3 L432-435 / §C4: mechanical where safe, surfaced where judgment needed. + // Mechanical where safe, surfaced where judgment is needed. expect(f).toMatch(/manual-review diagnostic/i); expect(f).toMatch(/flatten|split/i); }); it("declares runtime data greenfield (no receipt-data migrator)", () => { - // delta.md §C5 L443-449: source text only; runtime data abandoned. + // Source text only; runtime data is abandoned, not migrated. expect(f).toMatch(/greenfield/i); expect(f).toMatch(/source text only|source text/i); expect(f).toMatch(/no receipt-data migrator|abandoned, not (migrated|converted)/i); }); it("retires the responsibility status/pressure store from Current Conventions", () => { - // delta.md §B4: no status enum, no pressure. + // No status enum, no pressure. const conventions = doc.slice( doc.indexOf("## Current Conventions"), doc.indexOf("## History"), @@ -191,12 +195,12 @@ describe("changelog.md — the upgrade mechanism (delta.md Part C)", () => { }); }); -describe("help.md — scrubbed of removed kinds (delta.md §B6)", () => { +describe("help.md — scrubbed of removed kinds", () => { const doc = read("help.md"); const f = flat(doc); it("frontmatter-style kind enum no longer offers service or system", () => { - // delta.md §B1: service->function, system deleted. + // service -> function; system deleted. const kindLine = doc.split("\n").find((l) => l.includes("kind:") && l.includes("|")) ?? ""; expect(kindLine).not.toContain("service"); @@ -206,7 +210,8 @@ describe("help.md — scrubbed of removed kinds (delta.md §B6)", () => { }); it("teaches the responsibility (Requires->Maintains) and function (Parameters->Returns) interfaces", () => { - // plan.md §4 L110-L112. + // The two interfaces contract-markdown.md defines: Requires -> Maintains + // and Parameters -> Returns. expect(doc).toContain("### Maintains"); expect(doc).toContain("### Parameters"); expect(doc).toContain("### Returns"); @@ -214,32 +219,33 @@ describe("help.md — scrubbed of removed kinds (delta.md §B6)", () => { }); it("states there is no system kind and gives the composition replacement", () => { - // plan.md §3 L105. + // Composition is intra-node call or cross-node subscription, never a + // system kind. expect(f).toMatch(/no `kind: system`|There is no `kind: system`/); expect(f).toMatch(/intra-node `call`|cross-node subscription/); }); it("collapses three levels of author control to two", () => { - // delta.md §B6 help.md: three-levels -> two; ### Wiring removed. + // Three levels of author control became two; ### Wiring removed. expect(f).toMatch(/Two levels of author control/i); expect(doc).not.toContain("### Wiring"); expect(f).not.toMatch(/Three levels of author control/i); }); it("does not present ### Ensures or ### Services as live authored sections", () => { - // delta.md §B2: Ensures->Maintains; Services deleted with system. + // Ensures -> Maintains; Services deleted with system. // (### Maintains is now the data-flow output section.) expect(doc).not.toContain("### Ensures\n"); expect(doc).not.toContain("- `report`: a concise answer with sources"); }); }); -describe("prosescript.md — KEEP, but vocabulary scrubbed (delta.md §B5/§B6)", () => { +describe("prosescript.md — kept, but vocabulary scrubbed", () => { const doc = read("prosescript.md"); const f = flat(doc); it("keeps the stable grammar core (the intra-node language is unchanged)", () => { - // delta.md §B5 L323: ProseScript stable, changes little. + // ProseScript is stable and changes little across the overhaul. // Spot-check load-bearing grammar productions survive. expect(doc).toContain("call_expr"); expect(doc).toContain("parallel_block"); @@ -249,13 +255,13 @@ describe("prosescript.md — KEEP, but vocabulary scrubbed (delta.md §B5/§B6)" }); it("frames ProseScript as the intra-node layer (call a function, not cross-node)", () => { - // plan.md §7 L150-L159: call is intra-node; cross-node is subscription only. + // call is intra-node; cross-node is subscription only. expect(f).toMatch(/intra-node/i); expect(f).toMatch(/never made in ProseScript|Cross-node connections are never/i); }); it("points the interface source at Maintains/Returns, not Ensures", () => { - // delta.md §B2. + // The interface sections are Maintains / Returns, never Ensures. expect(doc).toContain("### Maintains"); expect(doc).toContain("### Returns"); expect(doc).toContain("### Parameters"); @@ -268,9 +274,9 @@ describe("prosescript.md — KEEP, but vocabulary scrubbed (delta.md §B5/§B6)" }); }); -describe("deps.md + agent-onboarding.md — KEEP, survive the overhaul (delta.md §B6)", () => { +describe("deps.md + agent-onboarding.md — kept, survive the overhaul", () => { it("deps.md keeps the disk-only, lockfile, cycle-checked resolution model", () => { - // delta.md §B6 L360: deps survives wholesale; orthogonal to the overhaul. + // deps survives wholesale; it is orthogonal to the overhaul. const doc = read("deps.md"); const f = flat(doc); expect(f).toMatch(/No network calls during resolution|disk only/i); @@ -280,7 +286,7 @@ describe("deps.md + agent-onboarding.md — KEEP, survive the overhaul (delta.md }); it("agent-onboarding.md teaches an outcome contract without pinning implementation choreography", () => { - // delta.md §B6 L361: KEEP (+links); narrative already aligns. + // Kept, with links; the narrative already aligns. const doc = read("agent-onboarding.md"); const example = fencedMarkdownAfter(doc, "## What a contract looks like"); @@ -297,3 +303,17 @@ describe("deps.md + agent-onboarding.md — KEEP, survive the overhaul (delta.md expect(doc).not.toContain("kind: system"); }); }); + +describe("README.md — the skill version of record lives in SKILL.md", () => { + const readme = readFileSync(join(repoRoot, "README.md"), "utf8"); + + it("does not hardcode a skill version", () => { + // A literal "version 0.x.y" in the README went stale one release after it + // was written. The README points at SKILL.md frontmatter instead, so the + // version of record has exactly one home. + expect(readme).not.toMatch(/\bversion[:\s]+`?\d+\.\d+\.\d+/); + expect(readme).toMatch( + /`version:` frontmatter in \[`skills\/open-prose\/SKILL\.md`\]\(skills\/open-prose\/SKILL\.md\)/, + ); + }); +}); diff --git a/tests/open-prose/stale-docs/stale-docs.test.ts b/tests/open-prose/stale-docs/stale-docs.test.ts index 72711303..bdf1b930 100644 --- a/tests/open-prose/stale-docs/stale-docs.test.ts +++ b/tests/open-prose/stale-docs/stale-docs.test.ts @@ -7,14 +7,14 @@ // retired kind (`kind: service` / `kind: system`) is taught as LIVE behavior in // ANY SKILL doc — frontmatter `kind:` values, fenced code-block examples that // declare them, or prose that instructs using them as a current kind. The -// retired kinds are deleted (delta.md §B1 L273-281, Part F L534-541): +// retired kinds are deleted: // `service`->`function`, `system` removed. // // HARDENED (v1gaps-guard-harden): the prior version inspected ONLY leading // frontmatter (`^kind:`), so ~18 body-level retired-kind references survived // the wave-1 sweep undetected. This now scans the full text of every SKILL doc // with a precise allowlist so it does NOT flag the legitimate HISTORICAL / -// NEGATION contexts that the migration record must keep (delta.md keep-rule): +// NEGATION contexts that the migration record must keep: // - ALLOWLISTED FILES: changelog.md (the migration map — it must name the // retired kinds to record that they were removed). // - NEGATION/MIGRATION LINES: any occurrence whose line (collapsed with its @@ -31,8 +31,8 @@ // // RUN: the repo-root vitest config discovers tests/open-prose/**/*.test.ts, so // `pnpm test:skill` picks this up. -import { readdirSync, readFileSync, statSync } from "node:fs"; -import { join } from "node:path"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { dirname, join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; @@ -71,13 +71,13 @@ function allDocs(dir: string): string[] { return out; } -describe("compiler/index.prose.md — kind: service -> kind: function (delta.md §B1)", () => { +describe("compiler/index.prose.md — kind: service -> kind: function", () => { const doc = read("compiler/index.prose.md"); const fm = frontmatter(doc); const f = flat(doc); it("frontmatter declares kind: function", () => { - // delta.md §B1 L275: `service` -> `function`. The compiler IS a pinned + // `service` -> `function`. The compiler IS a pinned // callable program with Parameters/Returns semantics. expect(fm).toMatch(/^kind:\s*function\s*$/m); }); @@ -88,20 +88,20 @@ describe("compiler/index.prose.md — kind: service -> kind: function (delta.md }); it("uses the function call interface (### Parameters -> ### Returns)", () => { - // delta.md §B2 L296 / plan.md §4 L112: callables declare Parameters -> Returns. + // Callables declare Parameters -> Returns. expect(doc).toContain("### Parameters"); expect(doc).toContain("### Returns"); expect(doc).not.toContain("### Ensures"); }); it("frames itself as the intelligent compile phase, not a Forme-wired system", () => { - // architecture.md §2 L78-93: compile phase is intelligent; not a node. + // The compile phase is intelligent; it is not a node. expect(f).toMatch(/intelligent compile phase/i); expect(f).toMatch(/is not a mounted node|not Forme-wired/i); }); it("emits the compile-phase IR (topology + canonicalizers + postconditions), not a judge-era manifest", () => { - // delta.md §B6 L338-339 / §A5 L219: topology + canonicalizers + validators; + // The compile-phase IR: topology + canonicalizers + validators; // deletes activations/criteria/formeManifests. expect(doc).toContain("topology"); expect(doc).toContain("canonicalizer"); @@ -115,25 +115,26 @@ describe("compiler/index.prose.md — kind: service -> kind: function (delta.md }); it("the source discoverer recognizes the new kind set only", () => { - // architecture.md §7.1 L267-278: no system kind, no service kind. + // No system kind, no service kind. expect(f).toMatch(/Recognize responsibility, function, gateway, pattern, test, and unknown/); expect(f).toMatch(/no system kind and no service kind/); }); it("forbids reintroducing the retired judge/verdict/pressure/fulfillment beat", () => { - // world-model.md §3: do not reintroduce the judge; architecture.md §3.3. + // Do not reintroduce the judge; commit-gating is validators plus + // self-attestation. expect(f).toMatch(/reintroducing a judge \/ verdict \/ pressure \/ fulfillment-activation beat/i); expect(f).toMatch(/commit-gating is compiled postconditions plus render\s*self-attestation/i); }); it("derives wake_source from ### Continuity (input/self/external)", () => { - // world-model.md §5: one event, three sources. + // One event, three sources. expect(doc).toContain("wake_source"); expect(f).toMatch(/input-driven by default, self when a cadence is declared, external for a gateway/); }); }); -describe("guidance/authoring.md — reshaped to the new kind set (delta.md §B6 L357)", () => { +describe("guidance/authoring.md — reshaped to the new kind set", () => { const doc = read("guidance/authoring.md"); const fm = frontmatter(doc); const f = flat(doc); @@ -144,18 +145,18 @@ describe("guidance/authoring.md — reshaped to the new kind set (delta.md §B6 }); it("opens by enumerating only the five live kinds", () => { - // delta.md §B1: responsibility / function / gateway / pattern / test. + // responsibility / function / gateway / pattern / test. expect(f).toMatch(/`kind: responsibility`, `kind: function`, `kind: gateway`, `kind: test`, and\s*`kind: pattern`/); }); it("states there is no system kind and no service kind, with the replacement", () => { - // plan.md §3 L105: composition is call (intra-node) or subscription (cross-node). + // Composition is call (intra-node) or subscription (cross-node). expect(f).toMatch(/no\s*`kind: system`\*\* and \*\*no `kind: service`/); expect(f).toMatch(/imperative `call` \*inside\* a render or a cross-node \*subscription\*/); }); it("teaches the responsibility interface as ### Requires -> ### Maintains (+ ### Continuity)", () => { - // delta.md §B1/§B2: responsibility gains ### Requires -> ### Maintains. + // A responsibility's interface is ### Requires -> ### Maintains. const section = doc.slice( doc.indexOf("## Responsibility Authoring"), doc.indexOf("## Function Authoring"), @@ -168,7 +169,7 @@ describe("guidance/authoring.md — reshaped to the new kind set (delta.md §B6 }); it("teaches the function interface as ### Parameters -> ### Returns (replacement for service)", () => { - // delta.md §B1 L275 / plan.md §6 L147. + // function replaces the retired service. const section = doc.slice( doc.indexOf("## Function Authoring"), doc.indexOf("## Composition Authoring"), @@ -180,20 +181,20 @@ describe("guidance/authoring.md — reshaped to the new kind set (delta.md §B6 }); it("has no ### System Authoring section and no ### State and Memory section", () => { - // delta.md §B6 L357: delete system-authoring + the State and Memory section. + // System authoring and the State and Memory section are deleted. expect(doc).not.toContain("## System Authoring"); expect(doc).not.toContain("## State and Memory Authoring"); }); it("folds memory into the persisted world-model (one truth per node), no ### Memory section", () => { - // delta.md §B2 L289 / world-model.md §9.4: ### Memory folds into the WM. + // ### Memory folds into the world-model. expect(doc).toContain("## World-Model and Freshness Authoring"); expect(f).toMatch(/persisted world-model \*\*is\*\* its memory/); expect(f).toMatch(/no separate `### Memory` section/); }); it("gateway authoring frames it as an external-driven responsibility", () => { - // delta.md §B1 L278: gateway gains explicit ### Continuity: external-driven. + // A gateway declares an explicit external-driven ### Continuity. const section = doc.slice( doc.indexOf("## Gateway Authoring"), doc.indexOf("## Pattern Authoring"), @@ -203,14 +204,14 @@ describe("guidance/authoring.md — reshaped to the new kind set (delta.md §B6 }); it("keeps the workspace-private / published-world-model-public core, with no-judge framing", () => { - // delta.md §B6 L357 keep core; world-model.md §3 / SHAPES §0: scratch never fingerprinted. + // The core survives; scratch is never fingerprinted. expect(f).toMatch(/Author public contracts before choreography/); expect(f).toMatch(/never fingerprinted/); expect(f).toMatch(/Reintroducing a judge \/ verdict \/ pressure \/ fulfillment beat/); }); it("drops the legacy ### Ensures-as-obligation framing in favor of Maintains/Returns", () => { - // delta.md §B2 L286: ### Ensures retired (re-purpose, not rename). + // ### Ensures retired (re-purposed, not renamed). expect(doc).not.toContain("### Ensures"); expect(f).toMatch(/Make every `### Returns` \/ `### Maintains` item an obligation/); }); @@ -227,7 +228,7 @@ describe("CORPUS GUARD — no retired kind is TAUGHT AS LIVE in any SKILL doc (f const RETIRED = /kind:\s*`?(service|system)`?/gi; // Files allowlisted wholesale: their JOB is to record the retired kinds. - // changelog.md is the migration map (delta.md §C3) — it must name + // changelog.md is the migration map — it must name // `kind: service`/`kind: system` to document service->function and the // system deletion. This is the historical record, not live teaching. const ALLOWLIST_FILES = new Set(["changelog.md"]); @@ -247,7 +248,7 @@ describe("CORPUS GUARD — no retired kind is TAUGHT AS LIVE in any SKILL doc (f }); it("no SKILL doc teaches `kind: service` or `kind: system` as live behavior", () => { - // delta.md §B1 L273-281 + Part F L534-541: `service` and `system` are DELETED. + // `service` and `system` are DELETED. // A doc-sweep miss (this is exactly what stranded body references in SKILL.md, // prose.md, deps.md, and the examples) must fail here loudly. Scope: every *.md // under the SKILL, full text — frontmatter AND body — except the allowlisted @@ -286,3 +287,91 @@ describe("CORPUS GUARD — no retired kind is TAUGHT AS LIVE in any SKILL doc (f ).toEqual([]); }); }); + +// Every file under a directory, whatever its extension (test sources included). +function allFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) out.push(...allFiles(full)); + else out.push(full); + } + return out; +} + +describe("no shipped file cites a private design document", () => { + // The design documents behind the Intelligent React overhaul live outside + // this repository. A citation such as "delta § B2" or "world-model § 3" + // points a reader at a file they cannot open, so shipped prose names an + // in-repo section instead. The pattern is assembled from parts so this + // file's own source does not trip the guard. + const PRIVATE_DOC_NAMES = ["delta", "architecture", "world-model", "plan"]; + const PRIVATE_CITATION = new RegExp( + `\\b(?:${PRIVATE_DOC_NAMES.join("|")})\\.md\\b|\\bSHAPES(?:\\.md)?\\s*§`, + ); + // The upgrade record may name whatever it needs to route a migration. + const EXEMPT = new Set(["skills/open-prose/changelog.md"]); + const SCANNED_EXTENSIONS = [".md", ".ts", ".mjs", ".sh", ".json"]; + + function targets(): string[] { + const files: string[] = [ + ...allDocs(skillDir), + ...allDocs(join(repoRoot, "spec")), + ...allFiles(join(repoRoot, "tests/open-prose")), + ...allFiles(join(repoRoot, "scripts")), + ]; + for (const root of ["README.md", "CONTRIBUTING.md", "AGENTS.md"]) { + const full = join(repoRoot, root); + if (existsSync(full)) files.push(full); + } + return files.filter((f) => SCANNED_EXTENSIONS.some((ext) => f.endsWith(ext))); + } + + it("finds the skill, the spec, the tests, and the root docs (sanity)", () => { + const rels = targets().map((f) => relative(repoRoot, f)); + expect(rels).toContain("skills/open-prose/contract-markdown.md"); + expect(rels).toContain("spec/01-Language.md"); + expect(rels).toContain("tests/open-prose/stale-docs/stale-docs.test.ts"); + expect(rels).toContain("README.md"); + }); + + it("no skill doc, spec chapter, test, script, or root doc cites one", () => { + const offenders: string[] = []; + for (const path of targets()) { + const rel = relative(repoRoot, path); + if (EXEMPT.has(rel)) continue; + readFileSync(path, "utf8") + .split("\n") + .forEach((line, i) => { + if (PRIVATE_CITATION.test(line)) offenders.push(`${rel}:${i + 1} — ${line.trim()}`); + }); + } + expect(offenders, `private design documents cited:\n${offenders.join("\n")}`).toEqual([]); + }); +}); + +describe("relative markdown links in spec/ and skills/ resolve", () => { + // A link a stranger cannot follow is as bad as a private citation. Every + // `[text](path)` whose target is a relative path must name a file that + // exists in this repository; anchors, mail links, and absolute URLs are not + // checked here. + const LINK = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; + + it("every relative link target exists on disk", () => { + const offenders: string[] = []; + const docs = [...allDocs(join(repoRoot, "spec")), ...allDocs(skillDir)]; + for (const path of docs) { + const text = readFileSync(path, "utf8"); + for (const match of text.matchAll(LINK)) { + const target = match[1]; + if (/^(?:[a-z]+:|#)/i.test(target)) continue; + const file = target.split("#")[0]; + if (!file) continue; + if (!existsSync(resolve(dirname(path), decodeURIComponent(file)))) { + offenders.push(`${relative(repoRoot, path)}: ${target}`); + } + } + } + expect(offenders, `dangling relative links:\n${offenders.join("\n")}`).toEqual([]); + }); +}); diff --git a/tests/open-prose/state/prose-state.test.ts b/tests/open-prose/state/prose-state.test.ts index 1aa7a604..41d229f0 100644 --- a/tests/open-prose/state/prose-state.test.ts +++ b/tests/open-prose/state/prose-state.test.ts @@ -2,14 +2,14 @@ // (state/{README,filesystem,sqlite,postgres,in-context}.md). // // These assert the docs embody the Intelligent React end-state: -// - world-model.md §1: the canonical world-model is the truth; SQL/vector are +// - the canonical world-model is the truth; SQL/vector are // DERIVED PROJECTIONS, never the truth; published is fingerprinted, workspace // scratch never is. -// - delta.md Part B §B6 + Part F "State shape": re-point bindings/ to the +// - the state shape: re-point bindings/ to the // canonical world-model artifact; add a canonical-serialization-before- // fingerprint pass; reframe copy_binding -> "write world-model + sign // receipt"; delete the policy/responsibility-status/pressure registry. -// - architecture.md §5.2/§10: deterministic canonical serialization. +// - deterministic canonical serialization. // // Doc-conformance only (read the source docs, assert on content), matching // tests/open-prose/contract-markdown/contract-markdown.test.ts. @@ -36,7 +36,7 @@ const SQLITE = "state/sqlite.md"; const PG = "state/postgres.md"; const INCTX = "state/in-context.md"; -describe("the truth is canonical; SQL/vector are derived projections (world-model.md §1)", () => { +describe("the truth is canonical; SQL/vector are derived projections", () => { it("README frames SQL/vector/dashboards as derived projections, never the truth", () => { const f = flat(README); expect(f).toMatch(/derived projection/i); @@ -64,7 +64,7 @@ describe("the truth is canonical; SQL/vector are derived projections (world-mode }); }); -describe("workspace vs published is fingerprint-materiality, not visibility (delta.md Part F)", () => { +describe("workspace vs published is fingerprint-materiality, not visibility", () => { it("filesystem reframes workspace as never-fingerprinted private scratch", () => { const f = flat(FS); expect(f).toMatch(/never fingerprinted/i); @@ -85,7 +85,7 @@ describe("workspace vs published is fingerprint-materiality, not visibility (del }); }); -describe("canonical-serialization-before-fingerprint pass (architecture.md §5.2/§10)", () => { +describe("canonical-serialization-before-fingerprint pass", () => { it("filesystem documents the deterministic serialization + canonicalize + fingerprint pass", () => { const f = flat(FS); expect(f).toMatch(/Canonical-Serialization-Before-Fingerprint Pass/i); @@ -102,7 +102,7 @@ describe("canonical-serialization-before-fingerprint pass (architecture.md §5.2 }); }); -describe("copy_binding reframed to write-world-model + sign-receipt (delta.md §B6 prose.md)", () => { +describe("copy_binding reframed to write-world-model + sign-receipt (prose.md)", () => { it("prose.md replaces copy_binding with commit_world_model", () => { const src = read(PROSE); expect(src).toContain("commit_world_model"); @@ -122,7 +122,7 @@ describe("copy_binding reframed to write-world-model + sign-receipt (delta.md § }); }); -describe("render-harness seam: read prior WM by reference, scratch never fingerprinted (architecture.md §7.3)", () => { +describe("render-harness seam: read prior WM by reference, scratch never fingerprinted", () => { it("prose.md adds the render harness seam reading the prior WM by reference", () => { const f = flat(PROSE); expect(f).toMatch(/Render Harness Seam/i); @@ -133,7 +133,7 @@ describe("render-harness seam: read prior WM by reference, scratch never fingerp }); }); -describe("the judge/pressure activation envelope is retired (delta.md §B4, Part F)", () => { +describe("the judge/pressure activation envelope is retired", () => { it("prose.md drops the judge/pressure status env vars", () => { const src = read(PROSE); expect(src).not.toContain("PROSE_RESPONSIBILITY_STATUS_LATEST"); @@ -156,7 +156,7 @@ describe("the judge/pressure activation envelope is retired (delta.md §B4, Part }); }); -describe("the policy / responsibility-status / pressure registry is deleted (Part F: State shape)", () => { +describe("the policy / responsibility-status / pressure registry is deleted", () => { it("filesystem layout no longer carries state/responsibilities with status/pressure files", () => { const src = read(FS); expect(src).not.toContain("state/responsibilities/"); diff --git a/tests/open-prose/tenets/tenets.test.ts b/tests/open-prose/tenets/tenets.test.ts index 659d7cec..6ffb453f 100644 --- a/tests/open-prose/tenets/tenets.test.ts +++ b/tests/open-prose/tenets/tenets.test.ts @@ -1,6 +1,6 @@ // Conformance test for the RESHAPED SKILL doc, guidance/tenets.md. // -// tenets.md is a MIXED reshape (delta.md §B6 L356): keep/strengthen the aligned +// tenets.md is a MIXED reshape: keep/strengthen the aligned // tenets (Spring container-vs-framework — MORE load-bearing now; "nodes don't // discover each other"; "invariants over finally"; the bitter lesson) and refine // the colliding ones (Ensures-as-obligation -> split Maintains/Returns; @@ -9,8 +9,6 @@ // scratch, but a shared canonical world-model is correct). The judge / system / // pressure / status framing must be gone. // -// Justification: delta.md §B6 L356 + Part F; plan.md §2/§3/§5; architecture.md §1 -// (L13-L54), §2 (L78-L97), §3.1 (L111-L133); world-model.md §3 (L104-L177). // Doc-conformance style matching tests/open-prose/forme/forme.test.ts. // // RUN from the repo root: @@ -31,14 +29,14 @@ function doc(): string { describe("tenets.md — Spring container-vs-framework strengthened", () => { it("frames the container/framework split as the load-bearing cleavage", () => { const source = doc(); - // plan.md "the metaphor that is the design"; delta.md §B6 L356. + // The container/framework metaphor is the design, not an illustration. expect(source).toMatch(/container[- ]vs[.-]? ?framework/i); expect(source).toMatch(/more.*load-bearing/i); }); it("ties the framework to the SDK and the language to the SKILL render", () => { const source = doc(); - // architecture.md §1 L13-L25 (two layers). + // Two layers: the framework (SDK) and the language (SKILL render). expect(source).toMatch(/framework[^.]*SDK/i); expect(source).toMatch(/language[^.]*SKILL/i); }); @@ -47,7 +45,7 @@ describe("tenets.md — Spring container-vs-framework strengthened", () => { describe("tenets.md — refine the colliding tenets", () => { it("splits Ensures-as-obligation into Maintains (node) and Returns (call)", () => { const source = doc(); - // delta.md §B2 L286 (### Ensures -> ### Maintains, four jobs); plan.md §3. + // ### Ensures -> ### Maintains (four jobs) for nodes, ### Returns for calls. expect(source).toMatch(/### Maintains/); expect(source).toMatch(/### Returns/); expect(source).toMatch(/not.*(a )?rename of `?### Ensures`?|false-friend/i); @@ -55,7 +53,7 @@ describe("tenets.md — refine the colliding tenets", () => { it("collapses three levels of control to two", () => { const source = doc(); - // delta.md §B3 L310 (Level-2/Level-3 retired); plan.md §5. + // Level-2/Level-3 author control retired. expect(source).toMatch(/three.*two|collapsed from three to \*\*two\*\*/i); expect(source).toMatch(/declarative/i); expect(source).toMatch(/imperative/i); @@ -63,7 +61,7 @@ describe("tenets.md — refine the colliding tenets", () => { it("makes Forme intelligent at compile and the reconciler dumb at run", () => { const source = doc(); - // architecture.md §2 L84-L92; world-model.md §3 L120-L143. + // Intelligent at compile, dumb at run; no LLM in the change decision. expect(source).toMatch(/intelligent at compile/i); expect(source).toMatch(/dumb (on purpose|at run)/i); expect(source).toMatch(/never asks an LLM .did this change/i); @@ -71,7 +69,7 @@ describe("tenets.md — refine the colliding tenets", () => { it("keeps shared canonical world-model correct while forbidding shared scratch", () => { const source = doc(); - // SHAPES.md §0 invariant 3; architecture.md §5.2. + // Scratch is never fingerprinted; the canonical world-model is shared. expect(source).toMatch(/no shared \*?scratch\*?/i); expect(source).toMatch(/shared \*?canonical world-model\*? is.*correct/i); }); @@ -80,14 +78,14 @@ describe("tenets.md — refine the colliding tenets", () => { describe("tenets.md — the system kind and judge framing are retired", () => { it("declares there is no system kind", () => { const source = doc(); - // plan.md §3; delta.md §B1 L276. + // No system kind; the render atom is the only runnable unit. expect(source).toMatch(/no `?system`? kind/i); expect(source).toMatch(/render atom is the only runnable unit/i); }); it("does not reintroduce a judge/verdict/pressure/status maintenance loop", () => { const source = doc(); - // world-model.md §3 L142-L143 "do not reintroduce it"; delta.md Part F. + // The doc says "do not reintroduce it". // The judge/verdict/pressure loop is only ever named to FORBID it. expect(source).toMatch(/Do not reintroduce a judge/i); expect(source).toMatch(/no status enum/i); @@ -101,7 +99,7 @@ describe("tenets.md — the system kind and judge framing are retired", () => { describe("tenets.md — the bitter lesson as compile/run split", () => { it("reframes the bitter lesson as intelligence-at-compile, determinism-at-run", () => { const source = doc(); - // world-model.md §3 L115-L134 (React deps vs Object.is analogy). + // The React deps-array / Object.is analogy. expect(source).toMatch(/intelligence.*at compile.*determinism.*at run|compile.*run split/i); expect(source).toMatch(/Object\.is|deps array/); }); @@ -109,7 +107,7 @@ describe("tenets.md — the bitter lesson as compile/run split", () => { describe("runtime/judge-responsibility.prose.md — DELETED", () => { it("no longer exists on disk", () => { - // delta.md §B4 L320 / §B6 L364: the judge runtime service is deleted. + // The judge runtime service is deleted. const judgePath = join( repoRoot, "skills/open-prose/runtime/judge-responsibility.prose.md",