From 01bf19ecd31fe05f1397c7d30795397ee3b26d24 Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Wed, 2 Sep 2026 20:15:25 -0400 Subject: [PATCH 1/8] feat: make contract id: optional and hold every declared id to its format The format doc required id: on every responsibility and the compiler CLI preflighted for it, yet nothing downstream read the field: the IR keys nodes by slug, the state backends key the world model by node, and the fingerprint is over content. Sixty-seven of 86 example responsibilities had no id and half of the ids that existed were hand-typed slugs, so 12 of 29 examples refused to compile for a rule with no consumer. id: is now optional on responsibilities and gateways: the slug is the identity by default, and a declared id is the source identity that survives renames. The format doc, compiler instruction, CLI preflight, changelog conventions, and language spec all say the same thing. The eleven hand-typed slug ids in the corpus are dropped, since they named nothing the slug does not, and the two over-long compiler fixture ids are trimmed to the documented 26-character Crockford shape. scripts/mint-contract-id.mjs mints ids for authors who want one and repairs malformed ids in place. A new corpus suite walks every example and holds each declared id to the format, checks uniqueness, and keeps version: in semver form. --- scripts/mint-contract-id.mjs | 149 ++++++++++++++++++ skills/open-prose/changelog.md | 8 +- skills/open-prose/compiler/index.prose.md | 14 +- skills/open-prose/contract-markdown.md | 13 +- .../src/contract-registry.prose.md | 1 - .../src/contract-source-files.prose.md | 1 - .../forme-fixpoint/src/operator-pins.prose.md | 1 - .../forme-fixpoint/src/schedule-plan.prose.md | 1 - .../src/topology-change-reporter.prose.md | 1 - .../src/topology-maintainer.prose.md | 1 - .../src/topology-safety-auditor.prose.md | 1 - .../monorepo-ci/src/merge-gate.prose.md | 1 - .../monorepo-ci/src/package-build.prose.md | 1 - .../monorepo-ci/src/package-test.prose.md | 1 - .../monorepo-ci/src/workspace.prose.md | 1 - spec/01-Language.md | 12 +- .../multi-facet/competitor-monitor.prose.md | 2 +- .../multi-facet/funding-brief.prose.md | 2 +- .../contract-markdown.test.ts | 23 ++- .../examples-corpus/contract-identity.test.ts | 116 ++++++++++++++ 20 files changed, 315 insertions(+), 35 deletions(-) create mode 100755 scripts/mint-contract-id.mjs create mode 100644 tests/open-prose/examples-corpus/contract-identity.test.ts 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/changelog.md b/skills/open-prose/changelog.md index 7a972094..a09420fb 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 diff --git a/skills/open-prose/compiler/index.prose.md b/skills/open-prose/compiler/index.prose.md index 66930788..0093f4a7 100644 --- a/skills/open-prose/compiler/index.prose.md +++ b/skills/open-prose/compiler/index.prose.md @@ -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/contract-markdown.md b/skills/open-prose/contract-markdown.md index 12ff1df0..cbb9789f 100644 --- a/skills/open-prose/contract-markdown.md +++ b/skills/open-prose/contract-markdown.md @@ -752,11 +752,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 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..6d5fabca 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 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..ea11cf01 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 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/monorepo-ci/src/merge-gate.prose.md b/skills/open-prose/examples/monorepo-ci/src/merge-gate.prose.md index 6a4ceac2..c5917648 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 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..431925cd 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 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..311d52c1 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 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..b4197317 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 diff --git a/spec/01-Language.md b/spec/01-Language.md index 64594a99..99d8eb2f 100644 --- a/spec/01-Language.md +++ b/spec/01-Language.md @@ -401,12 +401,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: 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/contract-markdown/contract-markdown.test.ts b/tests/open-prose/contract-markdown/contract-markdown.test.ts index fa7a1b3f..4937521a 100644 --- a/tests/open-prose/contract-markdown/contract-markdown.test.ts +++ b/tests/open-prose/contract-markdown/contract-markdown.test.ts @@ -315,10 +315,29 @@ describe("contract-markdown format doc — composition + render body", () => { }); 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` file also declares required `id:`/, + /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( + /`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/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([]); + }); +}); From f2c078f32bfaba495359d87821049165c120d7d3 Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Wed, 2 Sep 2026 20:23:09 -0400 Subject: [PATCH 2/8] fix: fold every example section into the canonical set Twenty-two headings across 18 example contracts were near-misses of a canonical section, so the VM lowered them to plain documentation instead of the semantics their authors meant. Continuity headings that carried the driven-ness in the title become a bare Continuity section with the driven-ness as its first bullet. Postconditions bullets fold into Maintains. Facets become named parts under Maintains, each with a material boundary. Failure containment becomes Invariants, a Continuity recheck becomes the self-driven bullet, and a gateway's watched globs move into Receives. A new corpus suite walks every example and holds each ### heading to the canonical table in the format doc, cross-checking its own list against the table so the two cannot drift apart. The three inbound- email examples join the intelligent-react shape suite, and the four implementation-pipeline contracts join the facet-named-parts suite, so no example is invisible to CI. --- .../src/runtime-watch.prose.md | 14 +- .../basic-unit-suite/src/alert-state.prose.md | 15 +- .../src/counter-events.prose.md | 14 +- .../src/contract-source-files.prose.md | 4 +- .../forme-fixpoint/src/operator-pins.prose.md | 4 +- .../src/human-review-events.prose.md | 4 +- .../src/star-events.prose.md | 4 +- .../src/construction-review.prose.md | 7 +- .../src/foundation-builder.prose.md | 8 +- .../src/implementation-work-plan.prose.md | 36 ++-- .../src/planning-corpus.prose.md | 26 ++- .../monorepo-ci/src/merge-gate.prose.md | 20 +- .../monorepo-ci/src/package-build.prose.md | 20 +- .../monorepo-ci/src/package-test.prose.md | 16 +- .../monorepo-ci/src/workspace.prose.md | 17 +- .../src/product-signal-inbox.prose.md | 4 +- .../oblique-weave/src/weave-config.prose.md | 4 +- .../surprise-cost/src/signals.prose.md | 4 +- .../canonical-sections.test.ts | 171 ++++++++++++++++++ .../examples-corpus/facet-named-parts.test.ts | 26 +++ .../intelligent-react-examples-corpus.test.ts | 10 +- 21 files changed, 335 insertions(+), 93 deletions(-) create mode 100644 tests/open-prose/examples-corpus/canonical-sections.test.ts 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/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/forme-fixpoint/src/contract-source-files.prose.md b/skills/open-prose/examples/forme-fixpoint/src/contract-source-files.prose.md index 6d5fabca..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 @@ -33,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 ea11cf01..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 @@ -28,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/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/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/monorepo-ci/src/merge-gate.prose.md b/skills/open-prose/examples/monorepo-ci/src/merge-gate.prose.md index c5917648..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 @@ -25,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 431925cd..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 @@ -41,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 311d52c1..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 @@ -27,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 b4197317..f5e3a1e9 100644 --- a/skills/open-prose/examples/monorepo-ci/src/workspace.prose.md +++ b/skills/open-prose/examples/monorepo-ci/src/workspace.prose.md @@ -13,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. @@ -25,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 @@ -53,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/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/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/facet-named-parts.test.ts b/tests/open-prose/examples-corpus/facet-named-parts.test.ts index 18c956de..6370f705 100644 --- a/tests/open-prose/examples-corpus/facet-named-parts.test.ts +++ b/tests/open-prose/examples-corpus/facet-named-parts.test.ts @@ -98,6 +98,32 @@ 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)", () => { 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..57c0395a 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,10 @@ const OWNED_EXAMPLES = [ // green, gated ledger-replay tests. "agent-observatory", "basic-unit-suite", + // 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 +51,10 @@ const OWNED_EXAMPLES = [ "masked-relay", "monorepo-ci", "oblique-weave", + "press-desk", "renewal-risk", "research-tree", + "support-inbox-router", "surprise-cost", "tamper-forge", ]; From cc6838bdec6a3ccc1becab78c2c51ecf2dcb4170 Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Thu, 3 Sep 2026 11:12:07 -0400 Subject: [PATCH 3/8] fix: every example Requires now names a producer in its own set Three examples carried needs that Forme could never satisfy from their own contracts. competitor-activity named three signal feeds that existed only in parentheticals; research-inbox-triage named a question list and an owner roster nothing produced; agent-observatory named four adapter mount instances that lived only in its README. The format doc gains a short rule for facet families and per-entity mounts: a placeholder facet heading declares a family, a placeholder need subscribes to one member, and the harness binds the member at mount time while the compiler emits the family. Forme's matching step says the same. competitor-activity gains a signal-feeds gateway with one facet per signal, so the monitor's three needs resolve facet by facet. research-inbox-triage gains a research-registry gateway that brings the human-maintained questions and roster into the graph. agent-observatory rewrites its adapter needs in family form and stays at nine contracts. Each example reaches Forme with zero unsatisfied needs and an acyclic topology. The new contracts join the facet-named-parts suite, and competitor-activity joins the intelligent-react shape suite. --- skills/open-prose/contract-markdown.md | 35 +++++++++ .../src/runtime-adapter.prose.md | 29 +++++--- .../src/session-ledger.prose.md | 12 ++-- .../examples/competitor-activity/README.md | 24 ++++--- .../src/competitor-activity-monitor.prose.md | 18 ++--- .../src/signal-feeds.prose.md | 72 +++++++++++++++++++ .../research-inbox-responsibility.prose.md | 11 +-- .../src/research-registry.prose.md | 56 +++++++++++++++ skills/open-prose/forme.md | 7 ++ .../contract-markdown.test.ts | 20 ++++++ .../examples-corpus/facet-named-parts.test.ts | 12 ++++ .../intelligent-react-examples-corpus.test.ts | 5 ++ tests/open-prose/forme/forme.test.ts | 7 ++ 13 files changed, 273 insertions(+), 35 deletions(-) create mode 100644 skills/open-prose/examples/competitor-activity/src/signal-feeds.prose.md create mode 100644 skills/open-prose/examples/research-inbox-triage/src/research-registry.prose.md diff --git a/skills/open-prose/contract-markdown.md b/skills/open-prose/contract-markdown.md index cbb9789f..d076a92b 100644 --- a/skills/open-prose/contract-markdown.md +++ b/skills/open-prose/contract-markdown.md @@ -492,6 +492,41 @@ 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). +### 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 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/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/competitor-activity/README.md b/skills/open-prose/examples/competitor-activity/README.md index 90e008b3..201c500f 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 @@ -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"). ## 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..df70c250 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 @@ -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. - -- `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.)* +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 — 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 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/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/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/tests/open-prose/contract-markdown/contract-markdown.test.ts b/tests/open-prose/contract-markdown/contract-markdown.test.ts index 4937521a..b487c0e7 100644 --- a/tests/open-prose/contract-markdown/contract-markdown.test.ts +++ b/tests/open-prose/contract-markdown/contract-markdown.test.ts @@ -249,6 +249,26 @@ describe("contract-markdown format doc — Maintains teaches #### facets (delta. 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. 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 6370f705..896325f5 100644 --- a/tests/open-prose/examples-corpus/facet-named-parts.test.ts +++ b/tests/open-prose/examples-corpus/facet-named-parts.test.ts @@ -66,6 +66,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 +84,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"], 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 57c0395a..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 @@ -40,6 +40,11 @@ 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. diff --git a/tests/open-prose/forme/forme.test.ts b/tests/open-prose/forme/forme.test.ts index 9a617567..680b4773 100644 --- a/tests/open-prose/forme/forme.test.ts +++ b/tests/open-prose/forme/forme.test.ts @@ -88,6 +88,13 @@ describe("forme.md — scope relocation: intra-system wiring -> the responsibili 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. From 4686eca97a20d00059750ba1edc001e45e7a8cf6 Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Thu, 3 Sep 2026 11:18:13 -0400 Subject: [PATCH 4/8] docs: attribute described example topologies to the reference harness Eight example READMEs state node and edge counts that mounting src/ alone cannot produce; the numbers come from the reference harness's per-entity expansion. Each README now says so in the sentence that carries the count and points at the repository README's Harnesses section, and no Conformance expectations section states a count that src/ cannot mount. The corpus index says the same for the set. A new corpus suite walks every example README, counts the contracts src/ mounts, and requires any larger claim to share its sentence with the attribution, so a future README cannot over-claim silently. --- skills/open-prose/examples/README.md | 5 +- .../examples/agent-observatory/README.md | 5 +- .../examples/feedback-pulse/README.md | 6 +- .../implementation-pipeline/README.md | 7 +- .../examples/inbox-triage/README.md | 6 +- .../open-prose/examples/monorepo-ci/README.md | 5 +- .../open-prose/examples/press-desk/README.md | 5 +- .../examples/research-tree/README.md | 6 +- .../examples/support-inbox-router/README.md | 5 +- .../described-topologies.test.ts | 212 ++++++++++++++++++ 10 files changed, 244 insertions(+), 18 deletions(-) create mode 100644 tests/open-prose/examples-corpus/described-topologies.test.ts 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/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/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/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/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-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/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/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); + }); +}); From 9f0daa7059af1bdcdfb7a6860c93df503290410c Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Thu, 3 Sep 2026 11:22:06 -0400 Subject: [PATCH 5/8] docs: specify node identity, artifact locators, and the receipt cost shape The IR doc keyed every worked example by slug without saying what the node key is. It now states the two-identifier model: node is mount identity, unique within a manifest and defaulting to the slug for a single mount, while a declared frontmatter id: is the source identity behind it and is not emitted in a version 2 manifest. The same doc says what an artifact locator resolves against: the OpenProse root, for canonicalizers and postconditions alike. The reconciler concept doc gives the receipt cost field its sub-shape, including the surprise_cause that must equal the wake source, which is what makes cost-scales-with-surprise observable. Doc-conformance assertions pin each addition in the suite that already owns the file. No IR schema bump; the expected and invalid fixtures validate unchanged. --- skills/open-prose/compiler/ir-v0.md | 40 ++++++++++++++++++- skills/open-prose/concepts/reconciler.md | 2 +- tests/open-prose/compiler/compiler-ir.test.ts | 37 +++++++++++++++++ tests/open-prose/concepts/concepts.test.ts | 13 ++++++ 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/skills/open-prose/compiler/ir-v0.md b/skills/open-prose/compiler/ir-v0.md index 151c52b1..a18eee53 100644 --- a/skills/open-prose/compiler/ir-v0.md +++ b/skills/open-prose/compiler/ir-v0.md @@ -129,8 +129,9 @@ 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` @@ -140,6 +141,27 @@ 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: @@ -239,6 +261,20 @@ 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 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/tests/open-prose/compiler/compiler-ir.test.ts b/tests/open-prose/compiler/compiler-ir.test.ts index 25d37646..e17e6328 100644 --- a/tests/open-prose/compiler/compiler-ir.test.ts +++ b/tests/open-prose/compiler/compiler-ir.test.ts @@ -310,6 +310,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 // --------------------------------------------------------------------------- diff --git a/tests/open-prose/concepts/concepts.test.ts b/tests/open-prose/concepts/concepts.test.ts index 0c623ff7..126e1a0c 100644 --- a/tests/open-prose/concepts/concepts.test.ts +++ b/tests/open-prose/concepts/concepts.test.ts @@ -109,6 +109,19 @@ describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md expect(source).toMatch(/never a wake signal/i); }); + 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 (world-model.md §3 L167-172)", () => { expect(source).toMatch(/structured[- ]backing/i); expect(source).toMatch(/render prose \*from\* it/i); From a763f8d207ef44fc31d7dfc7c6d947debbf36674 Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Thu, 3 Sep 2026 11:40:48 -0400 Subject: [PATCH 6/8] docs: cite in-repo sections instead of private design documents Thirty shipped files, from the format doc and the state backends to the example contracts and the conformance tests, cited design documents by section that live outside this repository. A public clone should never point its readers at text they cannot open. Every citation now names the in-repo section that owns the rule, such as the named-parts rule in the format doc or the fingerprint rules in the reconciler concept, or the sentence simply stands on its own where it needed no support. Test titles that carried a citation are retitled. No links were added; the replacements are prose a stranger can follow. --- skills/open-prose/compiler/index.prose.md | 2 +- skills/open-prose/compiler/ir-v0.md | 82 +++++++++---------- skills/open-prose/contract-markdown.md | 59 ++++++------- .../auto-pocock/src/auto-pocock.prose.md | 2 +- .../examples/competitor-activity/README.md | 4 +- .../src/competitor-activity-monitor.prose.md | 4 +- .../src/session-to-prose.prose.md | 2 +- .../src/prepare-renewal-brief.prose.md | 2 +- skills/open-prose/guidance/authoring.md | 31 +++---- skills/open-prose/prose.md | 20 +++-- skills/open-prose/state/README.md | 2 +- skills/open-prose/state/filesystem.md | 31 +++---- skills/open-prose/state/in-context.md | 3 +- skills/open-prose/state/postgres.md | 5 +- skills/open-prose/state/sqlite.md | 5 +- tests/open-prose/compiler/README.md | 2 +- tests/open-prose/compiler/compiler-ir.test.ts | 34 ++++---- tests/open-prose/concepts/concepts.test.ts | 56 ++++++------- .../contract-markdown.test.ts | 82 +++++++++---------- .../examples-corpus-migration.test.ts | 55 +++++++------ .../examples-corpus/facet-named-parts.test.ts | 31 ++++--- .../vendor-renewal-watch.test.ts | 76 ++++++++--------- tests/open-prose/forme/forme.test.ts | 55 +++++++------ tests/open-prose/primitives/session.test.ts | 30 ++++--- .../responsibility-runtime.test.ts | 22 +++-- .../open-prose/skill-meta/skill-meta.test.ts | 57 +++++++------ .../open-prose/stale-docs/stale-docs.test.ts | 45 +++++----- tests/open-prose/state/prose-state.test.ts | 20 ++--- tests/open-prose/tenets/tenets.test.ts | 24 +++--- 29 files changed, 422 insertions(+), 421 deletions(-) diff --git a/skills/open-prose/compiler/index.prose.md b/skills/open-prose/compiler/index.prose.md index 0093f4a7..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 diff --git a/skills/open-prose/compiler/ir-v0.md b/skills/open-prose/compiler/ir-v0.md index a18eee53..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 @@ -133,10 +133,10 @@ 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. @@ -172,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) @@ -207,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/ @@ -219,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[]` @@ -244,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` @@ -255,7 +251,7 @@ 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 @@ -278,7 +274,7 @@ together or not at all. The same rule applies to the `artifact` field of every ## 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 { @@ -304,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 @@ -334,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/contract-markdown.md b/skills/open-prose/contract-markdown.md index d076a92b..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,8 +491,7 @@ 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 @@ -531,7 +532,7 @@ that family, not a second copy of the contract. `### 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 @@ -544,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. @@ -589,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 @@ -854,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/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/competitor-activity/README.md b/skills/open-prose/examples/competitor-activity/README.md index 201c500f..d850df3a 100644 --- a/skills/open-prose/examples/competitor-activity/README.md +++ b/skills/open-prose/examples/competitor-activity/README.md @@ -28,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; @@ -45,7 +45,7 @@ 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 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 df70c250..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 @@ -67,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/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/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/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/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 e17e6328..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**"); @@ -384,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. @@ -421,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); @@ -429,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. @@ -465,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/concepts/concepts.test.ts b/tests/open-prose/concepts/concepts.test.ts index 126e1a0c..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,7 +105,7 @@ 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); }); @@ -122,12 +122,12 @@ describe("reconciler.md — the dumb reconciler (delta.md §B6, architecture.md expect(source).toMatch(/`skipped` receipt carries zero cost/i); }); - it("states the structured-backing rule (world-model.md §3 L167-172)", () => { + 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); @@ -146,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\*\*/); @@ -170,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"); @@ -186,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); @@ -209,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 b487c0e7..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,7 +240,7 @@ 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); @@ -270,8 +268,8 @@ describe("contract-markdown format doc — Maintains teaches #### facets (delta. }); 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); @@ -279,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( @@ -289,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); @@ -299,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); @@ -315,20 +313,20 @@ 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/); }); 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 896325f5..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 @@ -138,7 +137,7 @@ const FACETED: { rel: string; facets: string[] }[] = [ }, ]; -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", () => { @@ -146,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:/); } @@ -163,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/); @@ -172,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)); @@ -221,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/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 680b4773..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,7 +82,7 @@ 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); @@ -97,7 +97,7 @@ describe("forme.md — scope relocation: intra-system wiring -> the responsibili 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); @@ -105,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/); @@ -116,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"); @@ -126,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); @@ -134,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/); @@ -149,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); }); @@ -158,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/); @@ -166,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); @@ -174,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/); @@ -200,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/); @@ -217,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); }); @@ -226,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..790018cf 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,18 @@ 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); + // 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); }); 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); }); @@ -112,12 +112,13 @@ describe("SKILL.md frontmatter — versioning (delta.md §C7)", () => { }); }); -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 +139,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 +192,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 +207,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 +216,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 +252,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 +271,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 +283,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"); diff --git a/tests/open-prose/stale-docs/stale-docs.test.ts b/tests/open-prose/stale-docs/stale-docs.test.ts index 72711303..7ddb3aa1 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 @@ -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 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", From fd9d822c250a0c449bd9396cd9c92cbbc875c2d0 Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Thu, 3 Sep 2026 11:40:48 -0400 Subject: [PATCH 7/8] docs: pin harness status facts and guard the spec against rot The spec marked implementation-status facts with the word "today" and no attribution, so they would rot silently now that the reference harness versions on its own. Six claims about poll cadence, the unwired commit gate, the thin v0 receipt, the flat serve loop, and the dropped Schedule section now name the reference harness and the version they describe, and point once per document at the repository README's Harnesses section. The two spec documents agree on a harness-chosen receipt ledger layout. Two ideation links and a stray phase marker are gone, the README's Harnesses link targets the harness spec, and its version sentence defers to SKILL.md as the version of record. The version script writes each manifest field on its own line instead of round-tripping the file through jq, so a bump no longer reflows the codex manifest; that manifest's capabilities array is collapsed back to one line. Three guards land in the suites that own the territory: no shipped file may cite a private design document, every relative link in spec/ and skills/ must resolve, and the README may not hardcode a skill version. --- .codex-plugin/plugin.json | 5 +- README.md | 4 +- scripts/bump-version.sh | 14 ++- spec/01-Language.md | 58 ++++++------ spec/03-AuthoringPattern.md | 64 +++++++------ .../open-prose/skill-meta/skill-meta.test.ts | 14 +++ .../open-prose/stale-docs/stale-docs.test.ts | 92 ++++++++++++++++++- 7 files changed, 184 insertions(+), 67 deletions(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index a2604adb..5064cfa8 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -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/spec/01-Language.md b/spec/01-Language.md index 99d8eb2f..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 | @@ -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/skill-meta/skill-meta.test.ts b/tests/open-prose/skill-meta/skill-meta.test.ts index 790018cf..caa5e030 100644 --- a/tests/open-prose/skill-meta/skill-meta.test.ts +++ b/tests/open-prose/skill-meta/skill-meta.test.ts @@ -300,3 +300,17 @@ describe("deps.md + agent-onboarding.md — kept, survive the overhaul", () => { 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 7ddb3aa1..bdf1b930 100644 --- a/tests/open-prose/stale-docs/stale-docs.test.ts +++ b/tests/open-prose/stale-docs/stale-docs.test.ts @@ -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"; @@ -287,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([]); + }); +}); From 2b25e71f00f2410fddeba42777f7749f787c40ac Mon Sep 17 00:00:00 2001 From: Jose Montes de Oca Date: Thu, 3 Sep 2026 11:44:05 -0400 Subject: [PATCH 8/8] chore: release skill 0.18.0 The format doc gained documented surface this cycle: id: is optional with the slug as default identity, version: is author-owned provenance, and facet families and per-entity mounts have a notation. The example corpus is compiler-clean under those rules and the keyless conformance suite proves it. That is a minor bump on the skill track. The changelog entry tells an author what changed in authored files and that a missing id: is no longer a compile error. runtime_contract stays at 2, so prose upgrade needs no source rewrite. The version script now touches exactly one line per manifest, which this bump demonstrates. --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- skills/open-prose/SKILL.md | 2 +- skills/open-prose/changelog.md | 33 +++++++++++++++++++ .../open-prose/skill-meta/skill-meta.test.ts | 7 ++-- 5 files changed, 41 insertions(+), 5 deletions(-) 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 5064cfa8..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", 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 a09420fb..9f8d14b5 100644 --- a/skills/open-prose/changelog.md +++ b/skills/open-prose/changelog.md @@ -47,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/tests/open-prose/skill-meta/skill-meta.test.ts b/tests/open-prose/skill-meta/skill-meta.test.ts index caa5e030..55a3e4ee 100644 --- a/tests/open-prose/skill-meta/skill-meta.test.ts +++ b/tests/open-prose/skill-meta/skill-meta.test.ts @@ -92,11 +92,13 @@ describe("skill-meta Markdown helpers", () => { describe("SKILL.md frontmatter — versioning", () => { const fm = frontmatter(read("SKILL.md")); - it("pins version to 0.17.0", () => { + 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", () => { @@ -109,6 +111,7 @@ describe("SKILL.md frontmatter — versioning", () => { 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); }); });