| Field | Value |
|---|---|
| RFC | 0144 |
| Title | Which host capability families the core schema declares |
| Status | Accepted |
| Author(s) | David Tufts (@davidscotttufts), with the openwop-app reference host |
| Created | 2026-08-09 |
| Updated | 2026-08-10 |
| Affects | schemas/capabilities.schema.json, spec/v1/host-extensions.md §"Canonical prefixes", spec/v1/host-capabilities.md, spec/v1/artifact-type-packs.md |
| Compatibility | additive per COMPATIBILITY.md §2.1 — five optional properties declared on an already-open object; five prose examples corrected to the spelling hosts already emit |
| Supersedes | — |
| Superseded by | — |
Status note.
Accepted(2026-08-10). The corpus change (five declarations, the §A rule, the prose corrections) landed with theActivecut; the wire shape was locked then.Acceptedwaited on one thing — a host witness that a declared family, advertised at the plain root, validates against the declared shape — and that witness now exists. See §"Acceptance witness". (Note the witness is the discovery-doc validation, distinct from RFC 0142's leg-B emission witness they share a subject with; see the witness section for the attribution.)
Summary
host-capabilities.md defines 36 distinct §host.<name> capability families. capabilities.schema.json declares 83 top-level properties. Sixteen families are declared in neither spelling — including five that carry normative MUSTs binding wire shape, so a host advertising them is validated by nothing and a consumer reading them has no schema to check against. This RFC declares those five (artifactTypes, aiEnvelope, agentRuntime, forms, promptLibrary) at the plain document root per RFC 0137 G16, states the rule that decides which of the remaining eleven ever earn a declaration, and corrects the five prose snippets that still show a dotted discovery key — the last of which is what actually blocks hosts from dropping their dotted mirrors.
Motivation
The defect is a missing enforcement surface, not a missing feature
This is the sixth instance of one corpus defect: a normative claim whose enforcement surface was never declared. RFCs 0138, 0139, 0140, 0141 and 0142 each closed one instance. This is the capability-discovery instance.
§host.artifactTypes says "A host advertising store: true MUST persist registered artifacts and emit artifact.created." That MUST binds a key that capabilities.schema.json has never heard of. Under additionalProperties: true the object validates whether the host spells it artifactTypes, host.artifactTypes, artifactType, or artifactTypez. The MUST is real; nothing anywhere states what shape carries it.
The measurement
Extracting ## §host.* headings from host-capabilities.md yields 40 headings, 36 unique — §host.knowledge and §host.secrets each appear three times (:1252/:2182/:2324 and :1329/:2259/:2401). Duplicate sections in a normative document are their own defect; they are recorded here because "the file's headings are the family list" needs the dedupe stated rather than assumed.
Reading each section's own advertised key — not its heading — partitions the 36:
A worked false positive, recorded because the method matters. An earlier pass of this analysis counted
httpas undeclared, reading the section title§host.http. That section advertises a family namedhttpClient, which is declared. Deriving the key from the heading is the same error class the surrounding RFCs were written to close — reasoning from the wrong surface — so the population here is derived from each section's capability-flag line and JSON example, never its title.
Not all sixteen should be declared, and that is the point
kanban, canvas, brand, launchStudio, entities are product concepts. They are documented in host-capabilities.md because packs bind to them, but the protocol has no normative claim about their wire shape and should not acquire one. Declaring all sixteen would pull vendor surface into the core schema; declaring none leaves five MUSTs unenforceable. The corpus needs a rule, not a curated list — a list is the artifact that drifts, which is how sixteen families accumulated undeclared in the first place.
Proposal
A. The rule (normative)
Added to host-extensions.md §"Canonical prefixes":
A
§host.<name>section inhost-capabilities.mdMUST be declared as a property ofcapabilities.schema.jsonwhen the section states a normative requirement (MUST / MUST NOT) that binds the shape or content of a wire artifact — a discovery-document field, an event payload, a request or response body. A section that only describes a host-sidectx.*method surface, or a product concept a pack may bind to, is an extension namespace: hosts MAY advertise it, clients MUST tolerate its absence, and it MUST NOT be declared in the core schema.
The rule is checkable against the tree, so a future sweep re-derives the same partition instead of re-litigating it, and it can become a CI gate later — deliberately — once the fleet has migrated.
B. The five declarations
Declared at the plain document root. Per RFC 0137 G16 (resolved 2026-08-05), host.<name> is the capability identifier notation — the spelling used in pack peerDependencies, in error.capability, and in prose § headings — while the discovery-document key is the plain family name at the document root. Five host capabilities were already declared plainly (fs, kvStorage, tableStorage, queueBus, scheduling), and the reference host emits artifactTypes un-prefixed today.
| Family | Declared shape | The MUST it carries |
|---|---|---|
artifactTypes | { supported, store, render, export[], types{} } | store: true ⇒ MUST persist and emit artifact.created (artifact-type-packs.md §"Host capability") |
forms | { contentPacks } | contentPacks ⇒ MUST instantiate through the host's own create path; pack strings are untrusted (form-content-packs.md §"Trust boundary") |
aiEnvelope | { supported, await } | generate is required when advertised; await only when await is advertised |
promptLibrary | { supported } | pinned-version lookup for replay determinism |
agentRuntime | { supported } | advertising it implies agents.manifestRuntime (RFC 0070 §B) |
Root additionalProperties stays true. This is a permissive first declaration: no host's current discovery document becomes invalid, including one still emitting a dotted 'host.forms' key. Each declared object sets additionalProperties: false on its own body, matching every sibling declaration in the file.
C. The prose corrections — the gating step, not tidy-up
Snippets in spec/v1/ show a dotted key in a discovery-document position:
| Location | Snippet |
|---|---|
artifact-type-packs.md:129, :149 | "host.artifactTypes": { … } — the store example, and the per-type types map |
host-capabilities.md:461, :479 | the same two shapes, mirrored |
host-capabilities.md:505 | "host.forms": { "contentPacks": true } |
host-capabilities.md:21 | §"The contract pattern" step 1 — "the host advertises host.<name>: { supported: true, … }" |
artifact-type-packs.md:172 | the store-without-render negotiation example, prose form |
A second scoping error, recorded because it is the RFC's own subject. The first sweep matched a quoted
"host.<name>key and returned exactly five. That predicate missed the last two rows, which write the same dotted key in backticks — and one of them,host-capabilities.md:21, is the document's general statement of the discovery pattern, which all 36 sections inherit. Correcting five leaf examples while the rule that generates them still reads dotted would have left the defect intact and looking closed. This is the same failure the RFC is about: a sweep whose predicate matched a narrower surface than the one that mattered.:21andhost-extensions.md's equivalent now state the identifier-vs-key distinction outright rather than demonstrating one spelling and meaning the other.Left dotted deliberately:
host-capabilities.md:24(error.capability = "host.<name>"— an identifier), the 36Capability flag:lines (identifiers), and everypeerDependenciesentry.
These corrections are load-bearing. A host reading :505 and emitting 'host.forms' is following the spec; its dotted key is not legacy debt but compliance with a snippet that still stands. Declaring the plain key while leaving the snippet in place lands a canonical spelling no host can safely emit exclusively — every host carries both keys indefinitely, and the fork looks closed in the schema while staying open on the wire. Hence an explicit ordering:
1. Declare plain (§B) — nothing newly invalid. 2. Correct the five snippets — the dotted spelling stops being spec-sanctioned. 3. Only then are dotted mirrors droppable, and dropping one is a host's decision, not a deprecation this RFC imposes.
Note that :129/:149 carry the store MUST that RFC 0142's leg B witnesses — the correction lands on the exact line that RFC's acceptance criterion points at.
Scoped, not swept. Fifteen quoted "host.<name> keys exist in spec/v1/; five are discovery-document keys and are corrected. The other ten are correct as written and MUST NOT be changed: eight are peerDependencies: { "host.X": "supported" } (the identifier notation, dotted-correct per G16), one is allowedHostCalls: ["host.fs", "host.kvStorage"], one is a frontend-plugin hostApi array. Three namespaces share a prefix; a regex-driven "fix the dotted keys" pass would convert all fifteen and break pack manifests — the same wrong-surface error, committed by the tool meant to correct it. Verifiable: grep -c '"host\.' spec/v1/ drops by exactly five, and no peerDependencies line changes.
Compatibility
Additive per COMPATIBILITY.md §2.1. Five optional properties on an object whose additionalProperties was and remains true; no field becomes required, changes type, or is removed. No MUST is added, relaxed, or reinterpreted — the five requirements have been normative since RFCs 0070, 0071, 0075 and 0137. A host emitting nothing new stays valid; a host emitting a dotted mirror stays valid; a host emitting a plain key is, for the first time, validated against a declared shape.
The prose corrections change no requirement — they correct the spelling of an example to the one the schema now declares and the reference host already emits.
Conformance
No new scenario. The declarations are exercised by spec-corpus-validity (every schema round-trips) and by the existing capability-gated legs that read these families.
Deliberately not added here: a leg asserting that a host emits the canonical arm. The RFC 0137 helper readFormContentCap resolves four arms (plain-root → dotted-root → plain-wrapper → dotted-wrapper) so the suite can measure a mixed fleet during exactly this migration. Tightening that shared resolver now would red every host at once and convert a migration aid into a gate this RFC has not earned. The correct instrument is a separate leg that asserts the canonical spelling, or reports which arm answered, so conformant and migrated stop being the same green — filed as a gap below rather than smuggled in as a helper change.
Acceptance criteria
- [x] Five families declared plainly;
spec-corpus-validityand the fullopenwop:checkgreen —artifactTypes,aiEnvelope,agentRuntime,forms,promptLibraryeach declared at the plain root inschemas/capabilities.schema.json. - [x] Five quoted-key snippets corrected and the two prose/pattern statements restated; the ten identifier occurrences unchanged.
- [x] The §A rule present in
host-extensions.md§"host.*capability surfaces" and consistent with the declared/undeclared partition above. - [x] A host advertising at least one newly declared family validates against the schema — witnessed on the openwop-app reference host (see §"Acceptance witness").
Acceptance witness
Witnessed 2026-08-10 on the openwop-app reference host. The host advertises artifactTypes (and forms) at the plain document root (discovery.ts:1406, RFC 0137 G16) and its emitted artifactTypes block validates against capabilities.schema.json's declared shape by Ajv 2020 (additionalProperties: false on the family body) — its test/artifact-types-advert-conformance.test.ts runs 3/3 green, and that file caught a real prior advert defect before this witness (an extra schemaEndpoint key + a non-object types map, since fixed), which is what makes the green non-vacuous rather than tautological. The @openwop/openwop-conformance@1.70.2 suite runs 2527 passed / 0 failed against the host under OPENWOP_REQUIRE_BEHAVIOR=true, including every artifact-type-* shape scenario.
Attribution (kept honest). The store: true / artifact.created substrate this family's subject sits on — RFC 0142's leg-B emission witness — was landed by a separate openwop-app session (host PRs #3111 advertising per-type store, #3115 emitting artifact.created off-contract). This RFC's witness is the discovery-document validation property, which is independent of that posture: a host need not take the store: true posture to witness that its declared artifactTypes block validates against the schema. RFC 0142 (the emission witness) reached Accepted separately (#920); this RFC rides on the shared subject but is credited for the validation half only.
Alternatives considered
1. Declare all seventeen. Pulls kanban, canvas, brand, launchStudio, entities into the core schema. These are product concepts with no protocol claim about their shape; the corpus would be asserting ownership it does not want. 2. Declare none; rely on additionalProperties: true. The status quo. Leaves five MUSTs binding keys nothing declares, which is the defect. 3. Declare dotted, matching the prose. Rejected on measurement: the reference host emits artifactTypes plain at discovery.ts:1338 while the prose shows it dotted, so following the prose would declare a node no host emits. G16 already settled the direction; the prose is the residue. 4. Set root additionalProperties: false. Would instantly invalidate every host carrying a dotted mirror — including one carrying it because the spec told it to. Correct only after step 3 of §C has played out across the fleet, and then only under its own RFC. 5. Ship a curated list instead of a rule. A list is the artifact that drifts. Seventeen undeclared families accumulated precisely because no rule said which ones should not.
Unresolved questions
1. Whether the eleven extension-namespace families should be marked as such in host-capabilities.md (a per-section badge) or left to the §A rule to classify on demand. A badge is more legible and is one more surface to drift. 2. Whether aiEnvelope.await belongs as a nested boolean (declared here) or as its own family. The prose spells the sub-flag host.aiEnvelope.await: supported; nesting matches forms.contentPacks and is the lower-variance reading, but no host advertises it today so the shape is unwitnessed. 3. When root additionalProperties: false becomes appropriate — gated on the dotted mirrors actually being dropped, which §C step 3 enables but does not schedule.
Open spec gaps
| ID | Gap |
|---|---|
| G1 | Arm visibility landed — CLOSED 2026-08-11. capability-arm-witness.test.ts reports which of the four resolver arms a host lands on for every declared family, and asserts that none is reachable only under the deprecated capabilities wrapper. It enforces an existing MUST rather than adding one: capabilities.md §"Document-root layout (normative — RFC 0073)" already states that "the conformance suite reads the root only … a host that serves families exclusively under the wrapper is non-conformant and is graded as such" — but every helper in the suite falls back through the wrapper arms, so the corpus said one thing and the code did another, and a host that never migrated read exactly like one that did. That gap between prose and resolver was this gap. Dotted-at-root is reported, not asserted: RFC 0137 G16 makes the plain name the discovery key, but the MUST is written about the root rather than the spelling at it, and additionalProperties: true tolerates the extra key — failing there would be a v1.x tightening this leg has no license for, so it is surfaced instead, which is the visibility this gap asked for. Sabotage-verified across six placements: canonical passes; root+wrapper mirrored passes (mirroring is a permitted v1.x affordance, so a leg that failed it would be wrong); root-dotted passes and reports; wrapper-only and wrapper-dotted both fail. What it still does not do: it cannot make a host migrate, and a host mirroring onto both arms is indistinguishable from one that has retired the wrapper — that distinction only becomes enforceable when the wrapper retires at v2.0. |
| G2 | Duplicate sections removed — CLOSED 2026-08-11. host-capabilities.md carried three §host.knowledge and three §host.secrets sections. They were not variants: within each family the copies were byte-identical except for a single Used by: line, and the duplicated region was one contiguous 284-line block (knowledge, secrets, knowledge, secrets) pasted after the originals. Consolidated to one section each, union-preserving: the surviving §host.knowledge keeps the specific consumer (vendor.myndhyve.knowledge-tools with its typeIds) and gains the pack categories that appeared only in the deleted copies. One thing was deliberately not carried over — the third §host.secrets copy listed "Future consumers: vendor.myndhyve.ads-publish-platform, vendor.myndhyve.ads-metrics-import", which the surviving copy supersedes by recording that the ads.publish.platform umbrella decomposed into three platform-specific packs. That line was stale rather than complementary; ads-metrics-import was speculative and is dropped with it, noted here rather than lost silently. No normative text changed — every MUST, capability flag and method contract in the surviving sections is byte-identical to what the duplicates carried. |
| G3 | The §A rule is now machine-enforced — CLOSED 2026-08-11. scripts/check-capability-declaration-classes.mjs runs in openwop:check against a reviewed ledger, spec/v1/capability-declaration-classes.json, classifying all 36 §host.<name> sections (25 declared / 11 extension). The gate deliberately does not classify. The §A trigger — binds the shape or content of a wire artifact — is a semantic judgement, and every heuristic reaching for it has been wrong in this corpus: §host.http advertises httpClient, so a heading-derived list calls it undeclared; §host.coordination names agentRuntime in prose to contrast itself with it, so a backtick-matching list calls it declared-by-that-key. Both false positives, the second found while building this gate — the same derive-from-a-plausible-proxy error the RFC records twice already. So a human decides once per section in a file a reviewer reads, and the machine guarantees the decision was made at all, still holds, and matches the schema. What it closes is narrow and exact: a new §host.<name> section can no longer land unclassified. It closes nothing about whether a given classification is right. Five checks, each sabotage-verified for its own cause: unclassified section, stale entry, declared key absent from the schema, extension family present in it, and — where the risk concentrates — an extension section stating a MUST without a recorded reason. That last check is what forced chat and knowledge to say why: both are ctx.*-only surfaces, and knowledge's 403 knowledge_workspace_forbidden is a host-side method error rather than a protocol response body, which is what keeps it out of the core schema. |
References
- RFC 0137 gap G16 (resolved 2026-08-05) — the plain-vs-dotted ruling this RFC applies
spec/v1/host-capabilities.md— the 36§host.<name>contractsschemas/capabilities.schema.json— 83 declared properties, rootadditionalProperties: true- RFC 0070 §B (
agentRuntime⇒agents.manifestRuntime), RFC 0071 / RFC 0075 (artifactTypesfacets), RFC 0137 (forms.contentPacks) - RFC 0142 — the
store-gated emission witness; its leg B is the intended first witness of a declaredartifactTypes - openwop-app ADR 0537 (2026-08-09) — reference host migrated
formsto the plain root, dotted key retained as a deprecated mirror