Summary
156 packs, 282 published versions, each a signed quad (manifest, SBOM, detached Ed25519 signature, tarball). 136 versions pin <2.0.0; 146 have no ceiling; none declares anything else — so under any honest rule every published version refuses on a v2 host and the re-publish wave is total, not optional. 280 of 282 use the deprecated publicKeyRef; 280 declare method: manual and 2 ed25519, which the schema calls synonymous and the verifier treats as signatures over different bytes; build-index defaults to the convention almost nobody uses. peerDependencies is an open string map with no reference to the capability vocabulary, which is how host.fs/fs and host.queueBus/queueBus both got signed into the tree, next to a third grammar (openwop.agents.memoryBackends) and facet paths used as identifiers. The registry validates every pack against a vendored copy of the node-pack schema that lacks the very hatch RFC 0138 added, and it is unpinned. Two of the 13 manifest schemas still forbid the vendor field host-extensions.md requires them to carry. Three gap tables hold decisions about chain pinning, child ownership, conditional visibility, i18n and provider-id precedence that this RFC makes.
Motivation
- The ceiling.
engines.openwopover 282 manifests:>=1.0.0145,>=1.0.0 <2.0.086,>=1.1.0 <2.0.047,>=1.1 <2.0.03 (a two-segment grammar the others do not use),>=1.1.01. Every byte is under a detached signature; nothing can be edited in place.node-pack-manifest.schema.jsonengines.openwopis a free string with an example, andadditionalProperties: trueon the block. - Three trust paths, one file that none of them would read. Canonical publication is a PR wave (
writeApi.supported: false,publishMethod: "github-pull-request");verify-signatures.mjs:135–142authorizes bysigningKeys[].permittedNamespaces; MyndHyve runs its ownPUT /v1/packs/…registry keyed onpublicKeyRef(routes/packs.ts:22–30) and a super-admin mirror that re-derivessignedByKeyIdat ingest (routes/openwopMirror.ts). Acompatibility-overlay.jsonwould need five per-namespace signatures and would still be invisible to the mirror. - Two conventions, one word.
registry-version-manifest.schema.json:132— "manualanded25519both denote a detached Ed25519 signature over canonical-JSON pack.json (synonymous in practice)";registry/scripts/verify-signatures.mjs:150–171—ed25519signs the whole gzipped tarball,manualsignspack.jsoninside it;build-index.mjs:190defaults to'ed25519'while 280 of 282 saymanual. Axiom 2, inside the trust path. - Identifiers.
node-pack-manifest.schema.json:102–108peerDependencies: { additionalProperties: string }— no enum, no pattern, no$ref. In the tree:aiProviders67,host.agentRuntime41,openwop.agents.memoryBackends11,host.fs2 /fs1,host.queueBus2 /queueBus1,host.aiEnvelope.await,aiProviders.imageGeneration,aiProviders.videoGeneration.agent-manifest.schema.json:58offershost.workspaceas an example of a legitimate requirement;host-capabilities.md:2186–2188reserves it and requires rejection withpack_peer_dependency_undefined. RFC 0169 §B.1–§B.2: the declaration file is the identifier andcheck-declaration.mjsfails on a peer-dependency key it does not name. - The hatch. RFC 0138 §Out of scope (
:92) and §Unresolved 4 (:176) deferagent-manifestandprompt-template"to their owners";agent-manifestis$ref'd fromnode-pack-manifest.schema.json:134, so a pack that may carryx-at its root may not carry it in its ownagents[]. The registry's vendorednode-pack-manifest.schema.json(openwop-registry/schemas/, 7 of 13 drifted, noCORPUS_TAG,check-vendored-sync.mjsonmain) hasadditionalProperties: falseat:320and:641with nopatternProperties— the registry rejects the field the protocol requires it to carry (H34 class). - Prefix and
$id..well-known/openwop-registry.jsonendpoints: five of six carry/v1/,publicKeydoes not;endpointAliases._notealready tells clients to consultendpoints. The 13 schema$ids are under/spec/v1/and immutable (RFC 0172 row C5.7); the version manifests themselves carry no$id(the charter's "every manifest$ids onspec/v1" is true of the schemas). - Three gap tables.
connection-packs.md:277UQ4 (two packs claiminggithub; the scenarioconnection-provider-resolutionnever tests two claimants; 12 connection packs published);form-content-packs.md:180–184G1 (a second expression language), G3 (no i18n), R1 (no reference host; one pack published);workflow-chain-packs.md:508–512WCP2 (135 of 282 versions are chains), WCP4, WCP5 (= RFC 0133 §Unresolved 2, whose siblings were resolved by persisting the resolved version in the ownership record). - Register state.
openwop.migration.C10.1is arenamewithcodemod: null— §G.1 fails the moment this child is Active unless it has one;kindis absent on 131 of 282 manifests whileindex.jsoncarries it;deprecations.jsonhas 32 rows where the umbrella prose says 26 (the six newer rows are RFC 0172/0174/0178's).
Proposal
§A. The ceiling and the tree
§A.1 Absent ceiling reads <2.0.0. A v2 host evaluating engines.openwop MUST treat a range with no upper bound as bounded by <2.0.0 and MUST refuse to install a version whose range does not admit the host's protocol major with pack_engine_unsupported (a registered error; pack_runtime_requirement_unmet stays for runtime, not protocol). The check runs at install, on every publication path — the canonical registry, a vendor registry's write API, and a mirror ingest — so a registry-side artifact cannot be bypassed. A v2 manifest MUST declare an explicit upper bound; the schema enforces the grammar (three-segment, >= lower, < upper).
§A.2 registry/v2/. The registry publishes a parallel tree registry/v2/packs/<name>/-/<version>.{json,sbom.json,sig,tgz} of re-signed manifests produced by the same build-pack-tarball.mjs --signed path (:312, Ed25519 over canonical JSON) with SBOMs regenerated, index regenerated, and signing.scheme explicit. A signed compatibility-overlay.json is rejected: verify-signatures.mjs authorizes by namespace, so one overlay cannot cover five namespaces, and the mirror path would never read it. The v1 tree is served read-only through Phase 5.
§A.3 The registry's prefix. The registry is a static tree and cannot negotiate by header; it is versioned by tree — the C.5 rule permits one mechanism, not both. .well-known/openwop-registry.json endpoints gains the /v2/ templates beside the /v1/ ones and is the negotiation: clients MUST resolve paths through endpoints; publicKey stays unversioned by design (keys are not protocol-versioned) and the map's note says so.
§B. Identifiers
§B.1 The pack peer-dependency identifier is the C.2 declaration-file key (RFC 0169 §B.1: root key ≡ peer-dependency identifier ≡ section anchor). pack_peer_dependency_undefined is raised for a key the declaration file does not name — the hardcoded prose list at host-capabilities.md:2184–2186 is retired with it; host.workspace is workspace (RFC 0169 §B.3). Facet paths (aiProviders.imageGeneration, host.aiEnvelope.await, multiAgent.executionModel.verifier) are not identifiers: a pack requires a family and names facets in peerDependenciesMeta.<family>.facets[].
§B.2 The alias table. spec/v2/peer-dependency-aliases.json is generated by running check-declaration.mjs over every published manifest; each unresolvable key becomes a row { alias, family, facets?, removalTrigger }. At filing the guaranteed rows are host.fs → fs, host.queueBus → queueBus (the dotted twins), openwop.agents.memoryBackends → agents.memoryBackends, host.workspace → workspace, and the three facet paths; aiProviders (67) and host.agentRuntime (41) follow C.2's rename of those two keys. A v2 host MAY resolve an alias through the table through the overlap and MUST NOT after v1 end-of-support (removalTrigger: v1-end-of-support).
§C. The manifest schema family
§C.1 The 13 schemas re-$id under https://openwop.dev/spec/v2/; the v1 $ids are immutable and served read-only (RFC 0172 C5.7). §C.2 The hatch. ^(x-|vendor\.) patternProperties on every pack-authored document: the ten that have it, plus agent-manifest and prompt-template (RFC 0138 G4 decided: same treatment; the $ref boundary at node-pack-manifest.schema.json:134 no longer stops the hatch). pack-lockfile (host-generated) and security-advisory (registry-authored) stay closed; prompt-ref is a leaf. §C.3 One scheme. signing.method is deleted; signing.scheme is required with the single value ed25519-canonical-json (the manual convention: a detached 64-byte Ed25519 signature over the canonical-JSON pack.json inside a deterministic tarball); the tarball-bytes convention is retired with the two packs that use it re-signed in registry/v2/; build-index has no default. §C.4 publicKeyRef is deleted (keyId only; row C10.1 with a codemod). §C.5 kind is required on every version manifest (the 131 absent are node; the codemod writes it). §C.6 The registry's vendored schemas are pinned (RFC 0176 §E.1) and re-synced from the tag before any v2 publication; the drift that dropped the hatch is closed in that sync.
§D. Provider-id precedence (RFC 0095 UQ4)
§D.1 A provider.id is unique per host. When an installed pack and a built-in, or two installed packs, define the same bare id the host MUST refuse the later registration with connection_provider_conflict — the version-based precedence in connection-packs.md clause 6 (:86–94) is deleted (Axiom 2: a rule that picks silently by version is two names for one thing). A connector MAY name a provider by its qualified form <packName>#<id> to disambiguate; a bare id resolves only when exactly one definition exists. Built-ins are the host's own pack for this rule.
§E. Chain, form, and content packs — reuse, do not mint
§E.1 Chains. A chain reference MUST pin an exact version per referenced typeId (core.ai.callPrompt@1.0.0); ranges are a v2.x additive follow-up (WCP2 decided). §E.2 Co-registered children are reference-counted by deterministic child id and deleted when the last parent is deleted; the ownership record persists the resolved version (RFC 0133 §Unresolved 1's mechanism) and is a C.9 store (WCP5 / RFC 0133 G5 decided). §E.3 The {{params.*}} portable deferral stays forbidden in v2.0; the follow-up additive RFC is named as G4 with the variable-binding recipe from workflow-chain-packs.md:512 (WCP4 decided as "not in v2.0", a decision with an owner). §E.4 Forms. Conditional visibility uses the edge-condition operator set (when: <EdgeCondition> on a field; no second expression language — RFC 0137 G1); label/title/description are i18n.md localized strings (G3); minLength/min/max/pattern are added (G2). §E.5 The five format values stay the core set (G4 stands).
§F. The MyndHyve cohort and the wave
38 vendor.myndhyve. packs / 40 versions, all >=1.0.0 <2.0.0, are re-published under myndhyve-internal-1 into registry/v2/ in Phase 4 with the host; the mirror and the PUT API apply §A.1 at install. The core wave (109 packs) is a scripted operation on the existing generators (gen--packs.mjs, build-pack-tarball.mjs) and lands in Phase 3 as the registry's evidence.
Migration table
| Row | Kind | v1 | v2 | Codemod | Persisted data |
|---|---|---|---|---|---|
openwop.migration.C10.1 | rename | signing.publicKeyRef | signing.keyId | openwop.codemod.pack-manifest-v2 (refuses when both are present and differ) | never-upgraded (re-signed in registry/v2/) |
openwop.migration.C10.2 | rename | x-openwop-form.credentialProvider (node config ui hint) | provider | openwop.codemod.node-pack-ui-hints | never-upgraded |
openwop.migration.C10.3 | rename | workflow definition config.outputArtifactType / config.chatCard | node.artifactType / node.chatCard | openwop.codemod.workflow-definition-v2 | translated |
openwop.migration.C10.4 | behavior | engines.openwop free string; absent ceiling read as open | absent ceiling ⇒ <2.0.0; pack_engine_unsupported at install on every path; explicit upper bound required | — (the codemod refuses a manifest with no v2-satisfiable range; the author declares) | never-upgraded |
openwop.migration.C10.5 | add | one tree registry/v1/ | registry/v2/ parallel tree; endpoints names both | — | never-upgraded |
openwop.migration.C10.6 | rename | four peer-dependency grammars (host. dotted, bare, openwop., facet paths) | declaration-file keys; peerDependenciesMeta.<family>.facets[]; alias table with v1-end-of-support removal | openwop.codemod.pack-manifest-v2 | never-upgraded |
openwop.migration.C10.7 | unify | signing.method: manual | ed25519 | sigstore (two byte conventions) | signing.scheme: ed25519-canonical-json | openwop.codemod.pack-manifest-v2 (refuses ed25519-over-tarball and sigstore: a re-sign, not a rewrite) | never-upgraded |
openwop.migration.C10.8 | require | kind absent ≡ node (131 of 282) | kind required | openwop.codemod.pack-manifest-v2 | never-upgraded |
openwop.migration.C10.9 | behavior | 13 schema $ids under /spec/v1/; hatch on 10 | $ids under /spec/v2/; hatch on every pack-authored document | — | not-persisted |
openwop.migration.C10.10 | behavior | clause 6 version precedence | connection_provider_conflict fail-closed; <packName>#<id> qualified form | — | not-persisted |
openwop.migration.C10.11 | add | none | form-content when: <EdgeCondition>, localized strings, minLength/min/max/pattern | — | not-persisted |
openwop.migration.C10.12 | require | chain reference pin RECOMMENDED | exact version per typeId MUST | — | unchanged (135 chain manifests already pin) |
openwop.migration.C10.13 | behavior | child ownership lifecycle open (WCP5) | reference-counted by deterministic id; ownership record persists the resolved version | — | unchanged |
openwop.migration.C10.14 | behavior | registry /v1/ on five of six endpoints | versioned by tree; endpoints is the negotiation; publicKey unversioned by design | — | not-persisted |
openwop.migration.C10.15 | require | registry vendored schemas unpinned, 7 of 13 drifted | pinned to a tag; re-synced before any v2 publication | — | not-persisted |
Persisted-data disposition
| Store | v1 artifact | Disposition |
|---|---|---|
registry/v1/packs/** (282 manifests, SBOMs, signatures, tarballs) | signed quads | never-upgraded; served read-only through Phase 5 |
registry/v2/packs/** | — | new; re-signed by the same keys under signingKeys[].permittedNamespaces |
registry/v1/index.json, .well-known/openwop-registry.json | index; endpoints map | index unchanged; the map gains the /v2/ templates |
MyndHyve internal registry catalog (PUT API + mirror ingest) | packs keyed on publicKeyRef | never-upgraded; §A.1 applied at install in Phase 4 |
Host-side installed-pack records and lockfiles (pack-lockfile) | installed v1 manifests | unchanged; a v2 host re-evaluates §A.1 at next install, not retroactively |
| Chain ownership records (RFC 0133) | resolved child versions | unchanged; §E.2 makes the record normative |
Workflow definitions with config.outputArtifactType / config.chatCard | deprecated bag entries | translated by openwop.codemod.workflow-definition-v2 on export; read through the definition adapter |
Compatibility
breaking (v2). This PR changes no v1.x wire shape.
Conformance
v2 scenarios (suite 2.0.0): manifest-ceiling-refused (gated on the seams-profile install path; §A.1 binds at install on every path, not on packs: an unbounded and a <2.0.0 manifest are both refused with pack_engine_unsupported; a >=2.0.0 <3.0.0 one installs), peer-dependency-declared (gated: an undeclared key is refused with pack_peer_dependency_undefined; an alias resolves through the overlap), manifest-hatch-carried (an x- field inside agents[] and prompts[] is ignored, not rejected), one-signing-scheme (registry corpus gate: every registry/v2/ manifest declares the scheme and verifies), provider-conflict (gated on connections.packsSupported: two claimants fail closed; the qualified form resolves), chain-pin-exact (a ranged reference is refused at register), form-when-reuses-edge-conditions (gated on forms: the when grammar is the edge-condition grammar). (Corrected 2026-09-28, Class 3, #1706: these read "gated on packs". packs advertises registry resolution, which a host installing only local packs correctly omits.)
Falsifiability — one row per normative requirement
| Requirement | Observable | Who can cause the condition | Verdict |
|---|---|---|---|
§A.1 unbounded/<2.0.0 refused at install | pack_engine_unsupported — openwop.requirement.0177.manifest-ceiling-refused | the suite, gated on the seams-profile install path; §A.1 binds at install on every path, not on packs (corrected 2026-09-28, #1706) | witnessable — gated |
§A.2 registry/v2/ verifies under the same keys | registry gate | the registry's registry-check.sh | witnessable — unaided (corpus) |
§A.3 endpoints names both trees | the well-known registry document | the suite, unaided | witnessable — unaided |
| §B.1 undeclared key refused; facet paths not identifiers | pack_peer_dependency_undefined — openwop.requirement.0177.peer-dependency-declared | the suite, gated on the seams-profile install path; the rule binds at install on every path, not on packs (corrected 2026-09-28, #1706) | witnessable — gated |
| §B.2 alias table generated, not hand-kept | check-declaration.mjs over the manifests — openwop.requirement.0169.declaration-parity | the corpus gate | witnessable — unaided (corpus) |
§C.2 hatch on agents[]/prompts[] | install accepts the x- field — openwop.requirement.0177.manifest-hatch-carried | the suite, gated on the seams-profile install path; the rule binds at install on every path, not on packs (corrected 2026-09-28, #1706) | witnessable — gated |
§C.3–§C.5 one scheme, keyId, kind | schema + registry gate — openwop.requirement.0177.one-signing-scheme | the corpus gate | witnessable — unaided (corpus) |
| §D.1 conflict fails closed | connection_provider_conflict — openwop.requirement.0177.provider-conflict | the suite via the RFC 0095 install/resolve seams | seam-gated |
| §E.1 exact pin | refusal at register — openwop.requirement.0177.chain-pin-exact, and …chain-pin-exact.external-range-refused for the EXTERNAL subChainRef branch, whose version the schema types as a semver range and therefore cannot refuse | the suite, gated on workflowChainPacks | witnessable — gated (the sibling-string legs are satisfied by schema validation alone, so this row reads host-enforced only for bundles cut on suite 2.4.1 or later) |
| §E.2 reference counting | host-side lifecycle | the suite via the chain seams | seam-gated |
§E.4 when is the edge-condition grammar | schema + a form pack fixture — openwop.requirement.0177.form-when-reuses-edge-conditions | the suite, gated on forms (no reference host: host-pending) | witnessable — gated |
Adversarial review
- Refusing 100% of published versions on a v2 host is a cliff, not a migration. Disposition: it is the honest reading of immutable data; the wave is scripted on generators that already exist;
registry/v2/lands in Phase 3 as the registry's own evidence before any host cuts; the v1 tree keeps serving v1 hosts. - A signed overlay is cheaper than re-signing 282 quads. Disposition: rejected on two facts — signatures authorize by namespace (five overlays, five keys) and the mirror path re-derives the signer at ingest and would never consult it; §A.1's install-time check is what makes the rule wire-observable regardless of which registry served the pack.
- Deleting
methodbreaks the twoed25519-over-tarball packs. Disposition: they are re-signed inregistry/v2/under the one scheme; the codemod refuses them rather than relabeling a signature over different bytes. - Facet paths as identifiers were legitimate under RFC 0105. Disposition: RFC 0169 §B.3 already decided the
aiProviders.*pair;peerDependenciesMeta.<family>.facets[]keeps the expressiveness with one identifier grammar. - The registry is "versioned by tree" while the protocol is "by header" — that is the both C.5 forbids. Disposition: C.5 forbids both mechanisms on one surface; the registry has no request handler to read a header and uses one mechanism (tree), the protocol uses one (header); the
endpointsmap is the indirection both share. - Fail-closed provider conflict breaks a host that legitimately ships a built-in
githuband installs the community pack. Disposition: the host names its built-in<hostPack>#githubor does not install the conflicting pack; silent precedence by version is the Axiom 2 failure UQ4 asked about; the qualified form is the disambiguation. - WCP4 is "decided" by deferring. Disposition: the decision is that v2.0 keeps the expansion-time MUST; the follow-up is registered as G4 with the recipe and an owner, which is what the three gap tables lacked.
- The registry's vendored schema drift is a registry bug, not a protocol rule. Disposition: it is both; §C.6 and RFC 0176 §E.1 make the pin a MUST so the H34 class cannot recur in the repository that gates publication.
Alternatives considered
- Read an absent ceiling as open (v2-compatible). Rejected: 146 versions would install on a host whose capability names they no longer match.
- A signed
compatibility-overlay.json. Rejected: §A.2, adversarial review 2. - Keep
methodwithmanualanded25519documented as different. Rejected: Axiom 2; one scheme, one set of bytes. - Scoped provider ids everywhere (
core.github,community.x.github). Rejected: renames 12 published connection packs for a conflict that fail-closed plus a qualified form already prevents.
Unresolved questions
- Whether
registry/v2/should carry only versions re-published by their authors or also steward re-signs ofcore.openwop.underopenwop-team-1. Recommended: steward re-signs forcore.andvendor.openwop*; vendor cohorts re-publish themselves.
Implementation notes (non-normative)
The alias table's first generation is a dry run of check-declaration.mjs over registry/v1/packs/**/-/*.json once spec/v2/declaration.json exists (Phase 3, RFC 0169). build-index.mjs:190's default is removed in the same registry PR that adds registry/v2/.
Acceptance criteria
- [x]
Draft → Active: RFC text; rowsC10.1–C10.15; three codemods with fixtures; four deprecation rows with detectors; ledger row; adversarial review. (This PR.) - [x]
Active → Accepted(Phase 3):spec/v2/core/packs.md; the 13 v2 schemas;registry/v2/with the core wave and a greenregistry-check.sh;spec/v2/peer-dependency-aliases.jsongenerated; the seven scenarios; the registry's schemas pinned. — evidence:registry-check.shgreen over 156 packs, 191 v2 and 282 v1 version manifests each signed by a key the registry permits for its namespace. The registry's pin was bumped tov2.4.3before this box was ticked (openwop-registry#67): it stood atv2.4.1, and ticking a "schemas pinned" clause against a two-release-stale pin would have claimed something unchecked.reason:the openwop-app clause is dropped from the tick. All three ids it names recordinapplicablethere — "host does not advertise conformance.seamsProfile: openwop-conformance-seams-v2" — the mounted-vs-built distinctionGOVERNANCE.mddraws, same as RFC 0176. This child is also the one that genuinely needs BOTH certified bundles:openwop.requirement.0177.provider-conflictisexecuted-passon MyndHyve ONLY — the reference host recordsinapplicable, it does not advertise the connections profile — while the ceiling, hatch, peer-dependency and chain-pin ids areexecuted-passon the reference host. Disclosed:form-when-reuses-edge-conditionsisinapplicableon all three with a stated reason, and its falsifiability verdict declareshost-pending, so RFC 0174 §B.1's second branch accounts for it rather than a pass.
References
spec/v1/node-packs.md§engines, §x-openwop-form;spec/v1/host-capabilities.md§"Reserved-but-undocumented surfaces";spec/v1/connection-packs.md§Manifest clause 6, UQ4;spec/v1/form-content-packs.md§Open spec gaps;spec/v1/workflow-chain-packs.md§Open spec gaps;spec/v1/registry-operations.md- RFC 0095 §B.6; RFC 0107; RFC 0133 §Unresolved; RFC 0137; RFC 0138 §Out of scope, §Unresolved 4; RFC 0167 §C/§F; RFC 0169 §B; RFC 0172 §A.2, C5.7; RFC 0176 §E.1
openwop-registry:registry/.well-known/openwop-registry.json,registry/scripts/verify-signatures.mjs:130–186,registry/scripts/build-index.mjs:190,scripts/build-pack-tarball.mjs:312,scripts/registry-check.sh,scripts/check-vendored-sync.mjs:17–18