Summary
spec/v2/ext/README.md defines Stable as a predicate over certified evidence, and scripts/check-ext-status-coherence.mjs enforces it. On 2026-09-27 all 17 pages on openwop.dev/spec/v2/ under "Extensions" read Draft, and none could ever become Stable:
- The predicate needs an
executed-passrow underopenwop.family.<key>. No scenario records one, for any family, core or ext.gateFamilyrecords onlyinapplicableorskipped, underopenwop.profile.family.<key>. - The ext READMEs say to advertise
extensions.<org>.<key>with a camelCase key such asrestTransport.extensionsKeyPatternis kebab-case, so no host can emit that key. The two hosts that serve a family already use the kebab form: MyndHyve'smyndhyve.rest-transportandmyndhyve.chat. - The README requires a host at evidence tier 2 or better. The checker accepts any certified bundle, including a loopback reference host.
- Four of the 17 pages are not families. Three are notes, outside the rule;
portabilityis a core family, and #1671 retired its page. The notes still saidDraft, which promised a graduation they can never have.
This RFC does four things:
- names the key (§A);
- defines the witness for each class of ext family (§B, §C);
- makes the tier half of the predicate machine-checkable (§D);
- gives notes their own label (§E).
Motivation
Facts as of origin/main 54588558, 2026-09-27:
- **No
openwop.family.*row in any bundle.** None of the eight committed bundles inevidence/v2-host-bundles/has one. They carry 153requirement, 63scenario, 25itand 20floorrows (openwop-workflow-engine), and the same prefixes elsewhere. - The advertised keys. app.openwop.dev serves
extensions= {openwop-app.host,openwop-app.host-surfaces,openwop-app.ai-providers}. Its host-surface list nameshost.canvas,host.chat,host.kanban,host.knowledge,host.launchStudio,host.messagingandhost.webResearch, but none as an extension record. MyndHyve's v2 document servesmyndhyve.chatandmyndhyve.rest-transport(conditionalRunGet: true,contentEncodings: ["gzip","br"]), derived by its v2 projection asmyndhyve.<kebab(key)>. - The
a2uiSurfacewitness exists but is invisible. Its behavioral witnessv2-a2ui-v09-surfaceisexecuted-passin two certified bundles, the openwop-app side revisionrfc0199and the loopback reference host, both tier 1. No row ties it to the family. restTransporthas behavior but no v2 witness. RFC 0115's behavioral scenariorun-transport-economyexists only at major 1, reading the v1 root key.
Proposal
§A. The advertised key
§A.1 An ext family's declaration row carries extensionName, the kebab-case form of its key. A core row MUST NOT carry one (check-declaration.mjs). The family is advertised as extensions["<org>.<extensionName>"] (capabilities.md §3.2), never at the root.
§A.2 (Class 3 correction.) The ext READMEs, capabilities.md §3.2 and §6, and runs.md now use the kebab spelling. The camelCase key they printed matched no key a conforming host could emit, so no conforming host moves. Each README header now agrees with its declaration row by value (witness, technical, adoption) and names its advertised key. check-declaration.mjs checks both.
§A.3 a2uiSurface is the exception. Its contract is admitted through schemaVersions.kinds["ui.a2ui-surface"], and only its deprecated deltaTransport facet is an extensions record.
§B. The claims-check witness for the 11 reservations
A reservation defines no portable operation, so the witness can only check the claim. v2-ext-family-claims records openwop.family.<key> for each of brand, canvas, chat, coordination, dataIntegration, entities, kanban, knowledge, launchStudio, messaging and webResearch:
executed-passwhen every claim is well formed:
- the key matches extensionsKeyPattern; - the org is registered and not reserved; - the record is an object; - the family key is not also a root member.
executed-failwhen any claim is malformed.inapplicablewhen no registered org advertises the family.
A record under an unregistered org is not a claim on the family.
The gate is the claim itself, not behaviorGate. An ext family is outside every profile, so strict mode has nothing to demand of a host that does not serve it.
§C. Behavioral witnesses for restTransport and a2uiSurface
§C.1 restTransport's witness becomes witnessable-gated. spec/v2/ext/restTransport/README.md defines the record's two facets and what the claim adds to runs.md §"Caching and encoding":
conditionalRunGet: trueturns that section's SHOULDETaginto a MUST on every200.- Each listed coding MUST be produced when it is the only one requested.
v2-ext-rest-transport witnesses both under openwop.family.restTransport.
§C.2 a2uiSurface's witness becomes seam-gated. A new last leg of v2-a2ui-v09-surface records openwop.family.a2uiSurface, with an admitted positive and a refused control in one run.
§D. The tier half of Stable is machine-checked
§D.1 evidence/host-tiers.json lists hosts by discovery origin, with the tier GOVERNANCE.md §"Acceptance evidence tiers" gives each. An unlisted origin never qualifies. Adding a row is a governance act and cites the governance line.
§D.2 check-ext-status-coherence.mjs counts an openwop.family.<key> pass toward Stable only from a bundle that meets both conditions:
- it certifies a profile;
- its
discovery.urlorigin is listed at tier 2 or better.
It also enforces these rules:
- A
Stabledoc's declaration row saystechnical: stable, and aDraftdoc's does not. - A tier-1-only witness is reported as
NOT YET, never as graduable.
§E. Notes
A directory under spec/v2/ext/ with no declared family carries Status: Note. and says it is not a declared family. A note is outside the maturity rule and never Stable. A declared family is never a note. The three notes are grpc-transport, provider-idempotency and sandbox-runtime-notes. A retired page with no family (today portability) carries a Superseded by: or Retired by: line.
Compatibility
- §A is a Class 3 correction. The corrected text named a key that fails the discovery schema, so no host could have conformed to it. Both hosts that serve an ext family already emit the corrected spelling.
- §B–§C are
additive: new scenarios, one new leg, and new rows. A host that advertises no ext family recordsinapplicablethroughout. - §D–§E are corpus-gate changes. They fail closed, and no page changes status because of them.
- No wire change. No schema property,
requiredentry, error code or status moves.declaration.schema.jsongains one optional property.
Conformance
src/lib/ext-claims.test.ts(self-test, server-free) has nine cases:
- two well-formed claims; - absent, including a camelCase key and an unregistered org; - malformed: a reserved org, an array, a scalar or null record, a root copy of the key; - two orgs classified separately; - the corpus fact that every claims-check family has exactly one leg.
Removing the reserved-org or root-copy check turns its case red. Both sabotages were run.
v2-ext-family-claims(major 2): one leg per reservation, recorded underopenwop.family.<key>.v2-ext-rest-transport(major 2): conditional GET (a strongETag,304with no body, rotation across a real transition, and the parkedETagnot matching the completed run) and the per-coding byte round trip, both underopenwop.family.restTransport.v2-a2ui-v09-surface: the new family-witness leg.
Falsifiability — one row per normative requirement
| Requirement | Observable | Who can cause the condition | Verdict |
|---|---|---|---|
§A.1 kebab extensionName on every ext row, none on core | the declaration | the corpus | witnessable — corpus gate (check-declaration.mjs) |
| §A.2 README header agrees with its row and names its key | the README and the row | the corpus | witnessable — corpus gate (check-declaration.mjs) |
| §B a claim is well formed | the v2 discovery extensions object | the host | witnessable — gated on the claim (openwop.family.<key>) |
§C.1 conditionalRunGet: true ⇒ an ETag on every run-snapshot 200, and each listed coding is produced | response headers and bytes of GET /runs/{runId} | the suite, on the conformance-approval and conformance-noop fixtures | witnessable — gated on the claim |
| §C.2 the a2ui family leg | the emit-surface seam's answers | the suite, through the seam | witnessable — seam-gated |
§D a Stable label rests on a tier-2+ certified row | bundles and host-tiers.json | the corpus | witnessable — corpus gate (check-ext-status-coherence.mjs) |
§E a note is labeled Note, and a family never is | the READMEs | the corpus | witnessable — corpus gate (check-ext-status-coherence.mjs) |
Alternatives considered
- Accept tier-1 evidence for ext families. openwop-app is tier 1 and already serves 7 of the 11 reservations as host surfaces. Rejected: the README's tier-2 bar is the only thing separating
Stablefrom "the steward says so". Lowering it for the families least able to show interoperation would invert the rule. - A per-family
witnessIdslist in the declaration (count an existing row such asopenwop.scenario.v2-a2ui-v09-surface). This would graduate on bundles already committed, with no re-cut. Rejected: it names a second vocabulary for the same fact, and a scenario row folds every leg, so one unrelated leg could deny or grant the family. - Write a portable contract for each reservation first (for example
chat's operations). This is the honest route to interoperation. It is deferred rather than rejected: each is its own RFC, and a reservation'sStableis defined narrowly inext/README.mdso the label does not overclaim in the meantime. - Retire the reservations nobody serves. Not needed: MyndHyve, the tier-2 host, implements all 11 (Implementation notes).
Unresolved questions
chatis the reservation with the clearest cross-host demand. Does it get a portable contract (an RFC of its own) before or after itsclaims-checkgraduation?- MyndHyve serves
canvas,kanban,brandandentitiesfromcanvas-runtime, an origin that does no OpenWOP version negotiation. A reservation promises no operation, so advertising it from the v2 document promises nothing that origin must honour. Whether MyndHyve records that in the record body is its decision.
Implementation notes (non-normative)
- MyndHyve's v2 projection (
services/workflow-runtime/src/routes/discoveryV2.ts) already re-homes a non-core v1 root key toextensions["myndhyve.<kebab(key)>"]. Its v2 document dropsschemaVersions.kinds, soa2uiSurface's floor never reaches a v2 reader. It also serves no emit-surface seam, and its seams profile is deliberately off. - openwop-app lists the families as
host-surfacesentries, notextensionsrecords.
Acceptance criteria
- [x] Filed: the key, the witnesses, the tier table and the notes, with the self-test sabotage-proved.
- [ ]
Active: the comment window closes (2026-10-04) with no unresolved objection, and suite 2.42.7 is published. - [ ] Per family,
Draft → Stable(a separate promotion PR with its own 7-day window):check-ext-status-coherencereports the familyGRADUABLEfrom a committed, certified MyndHyve bundle cut against the DEPLOYED revision.
- Expected first: restTransport, chat, and the other reservations once MyndHyve advertises them. - a2uiSurface waits on MyndHyve's seam (gap G1).
- [ ]
Accepted: every row above witnessed. The §B/§C host rows areexecuted-passon a tier-2 certified bundle.
References
spec/v2/ext/README.md; RFC 0174 (governance predicates); RFC 0177 (the extension tail); RFC 0169 §B–§C (the declaration file, witness classes); RFC 0144 (extension-class families); RFC 0115 (run transport economy); RFC 0209 (A2UI v0.9 surfaces).GOVERNANCE.md§"Acceptance evidence tiers".