Summary
openwop distributes node packs, workflow-chain packs, and prompt packs, but it has no way to distribute the typed artifacts workflow nodes produce (documents, slides, app designs, CAD drawings) or the prompt-driven AI chat cards that generate them — both currently live as host-private runtime registries (MyndHyve) or hard-coded component switches (the OpenWOP demo app), keyed by unverifiable free strings. This RFC adds two new pack kinds — artifact-type and card — that make an artifact's schema + rendering hint + lifecycle, and a card's prompt template + output contract, distributable, signed, versioned, and capability-negotiable, and binds them to the already-shipped-but-contract-free nodes[].artifact.typeId, WorkflowNode.artifactType, WorkflowNode.cardType, and artifact.created.artifactType surfaces. It is strictly additive, phased (artifact-type packs first, chat card packs second), and deliberately stops at the wire contract: viewers, field widgets, and concrete export byte-production remain host-domain per positioning.md.
Motivation
Who hits this today. The one real non-steward adopter (MyndHyve) built an entire "Canvas Type" system — a manifest bundling node pack + card pack + artifact types — on top of openwop, but what it actually _published_ to the registry (vendor.myndhyve.canvas) encodes none of it: four opaque canvasCreate/Read/Write/crossCanvasInvoke CRUD nodes whose mutation/state shapes are additionalProperties: true blobs. The artifact-type and card models live entirely above the protocol, in a host-private runtime registry mapping free strings ("prd", "theme") to React viewers. The OpenWOP demo app independently re-implemented a cardType: string → component registry in chat/registry/. Two adopters, two incompatible private vocabularies, zero interop.
Why the spec is the right place. openwop already ships the _latent_ surface for both concepts but left it contract-free:
schemas/node-pack-manifest.schema.jsondefines aPackNode.artifactobject —{ typeId, syncOn, supportsCheckpoint }— with no normative prose innode-packs.mdand no conformance coverage.schemas/workflow-definition.schema.jsondefines first-classWorkflowNode.artifactTypeandWorkflowNode.cardTypefields, both free strings with no schema attachment and no registry binding.schemas/run-event-payloads.schema.jsonemitsartifact.createdwith a free-stringartifactTypeandadditionalProperties: true.spec/v1/host-capabilities.md§host.chat definesctx.chat.emitCard({ cardId, cardType, payload, idempotencyKey }), gated onhost.chat.cards: supported— the card _emission_ runtime exists, butcardTypeis a free-form string the spec only illustrates.
Two hosts using artifactType: "prd" or cardType: "prd.create" have no guarantee they mean the same shape. A free-string tag with a host-private schema is exactly the latent interop trap a wire contract exists to harden. This RFC closes the loop without inventing new primitives: it reuses the proven kind pack discriminator (node-packs.md §"Manifest format", which already distinguishes node/workflow-chain/prompt, each with its own manifest schema), the AI-envelope per-kind schema convention (ai-envelope.md §"Canonical schema location"), and the host.* capability pattern (host-capabilities.md §"The contract pattern").
Proposal
The RFC is phased. Phase 1 (artifact-type packs) is self-contained and the merge target for Active. Phase 2 (chat card packs) depends on Phase 1's outputArtifactType linkage and lands at a later Active milestone.
Mechanism: new pack kinds are additive, not a schema rewrite
Each existing pack kind ships its own manifest schema and its own kind const (node-pack-manifest kind: "node", workflow-chain-pack-manifest kind: "workflow-chain", prompt-pack-manifest kind: "prompt"). The registry already selects the validating schema by the manifest's kind and rejects mixed content with pack_kind_invalid. This RFC follows that pattern exactly: it adds two new manifest schemas, each with its own kind const, and does not modify node-pack-manifest.schema.json (its kind: "node" const stays). The only prose edit to node-packs.md is to extend the "Pack kinds" paragraph to name the new kinds.
Phase 1 — Artifact-Type Packs (kind: "artifact-type")
New doc spec/v1/artifact-type-packs.md (Status: DRAFT) and new schema schemas/artifact-type-pack-manifest.schema.json ($id: https://openwop.dev/spec/v1/artifact-type-pack-manifest.schema.json).
Manifest shape (kind: "artifact-type", required: ["name","version","kind","engines","artifactTypes"]):
+{
+ "kind": "artifact-type",
+ "name": "vendor.acme.cad",
+ "version": "1.0.0",
+ "engines": { "openwop": ">=1.1 <2.0.0" },
+ "artifactTypes": [{
+ "artifactTypeId": "vendor.acme.cad.model",
+ "schemaVersion": 1,
+ "schemaRef": "schemas/cad-model.schema.json",
+ "rendering": { "display": "file", "mimeType": "model/step" },
+ "exportFormats": ["step", "stl", "pdf"],
+ "syncOn": "completion",
+ "supportsCheckpoint": true
+ }]
+}
Normative requirements (full prose in spec/v1/artifact-type-packs.md):
- A manifest MUST set
kind: "artifact-type"and declare a non-emptyartifactTypes[]; it MUST NOT declarenodes[]/chains[]/prompts[](one kind per pack). - Each
artifactTypeIdMUST be reverse-DNS scoped;core.*is reserved. schemaRefMUST resolve to a Draft-2020-12 schema inside the tarball with top-leveladditionalProperties: falseand an$idunder{HostBase}/schemas/artifacts/{artifactTypeId}.schema.json.renderingis OPTIONAL and MUST reuse theai-envelope.md§"Rendering hints" vocabulary (advisory; consumers degrade gracefully;carddisplay reserved for envelopes). No second rendering vocabulary is introduced.exportFormatsis OPTIONAL format-identifier hints only; this RFC MUST NOT specify byte production.
Binding the contract-free surfaces (when host.artifactTypes: supported): a PackNode.artifact.typeId / WorkflowNode.artifactType / artifact.created.artifactType value that matches a registered artifactTypeId is _registered_ — the host MUST validate the payload against the pack schema before emitting artifact.created. A value matching no registered type remains valid (unregistered escape hatch, mirroring local.*): the host MUST NOT reject or schema-validate it, and SHOULD mark artifact.created.registered: false.
New host capability §host.artifactTypes advertises store/render/export independently:
"host.artifactTypes": { "supported": true, "store": true, "render": false, "export": ["pdf"] }
A host advertising render: false for a type it can store MUST still accept and store it and MUST NOT fail the run for lack of a renderer. This is the cross-host negotiation the spec lacks today.
artifact.created schema delta (run-event-payloads.schema.json): add optional registered: boolean (absent ⇒ true).
Phase 2 — AI Chat Card Packs (kind: "card")
A card is (inputs + prompt) → LLM call → typed artifact. New doc spec/v1/chat-card-packs.md (DRAFT) and schemas/chat-card-pack-manifest.schema.json.
Manifest shape (kind: "card", required: ["name","version","kind","engines","cards"]):
+{
+ "kind": "card",
+ "name": "vendor.acme.cad-cards",
+ "version": "1.0.0",
+ "engines": { "openwop": ">=1.1 <2.0.0" },
+ "peerDependencies": { "host.aiEnvelope": "supported", "host.chat.cards": "supported" },
+ "cards": [{
+ "cardTypeId": "vendor.acme.cad.model.create",
+ "prompt": { "template": "Design a model for: {{spec}}", "placeholderMapping": { "spec": "inputs.spec" }, "temperature": 0.2 },
+ "inputs": [{ "id": "spec", "type": "text", "label": "Part spec", "required": true }],
+ "outputArtifactType": "vendor.acme.cad.model",
+ "outputSchemaRef": "schemas/cad-model.schema.json"
+ }]
+}
Normative requirements:
kind: "card"with non-emptycards[]; reverse-DNScardTypeId.prompt.template+prompt.placeholderMappingREQUIRED; the host MUST substitute mapped inputs and route the call throughctx.aiEnvelope.generate(...).outputArtifactTypeOPTIONAL; when present MUST reference an installed artifact-type pack'sartifactTypeId(the Phase 1 linkage); the host MUST validate the LLM output against that type's schema and emitartifact.created.inputs[].typeis a closed portable enumtext|longtext|number|boolean|select|artifact-ref. MyndHyve'scanvas-reference/collection-referencewidget types and i18n keys are out of scope (host-UI). A host MAY add avendor.*-prefixed type others ignore.
Binding WorkflowNode.cardType and ctx.chat.emitCard (when new sub-flag host.chat.cardPacks: supported): a WorkflowNode.cardType / emitCard cardType value SHOULD reference a registered cardTypeId; existing free-form values ('progress', 'approval') remain valid. host.chat.cards behavior is unchanged.
Examples
Positive (artifact-type pack): the manifest above with a resolvable schemaRef (top-level additionalProperties: false). Negative (pack_kind_invalid): a manifest declaring both artifactTypes[] and nodes[]. Negative (schema error): artifactTypeId of core.openwop.cad.model from a non-core account; rendering.display of "3d-viewport" (closed enum).
Compatibility
Classification: additive per COMPATIBILITY.md §2.1.
- New pack kinds are new optional registry content with new manifest schemas;
node-pack-manifest.schema.jsonis unchanged. Hosts that don't support them advertise no capability and reject installs as today (§2.1: new endpoints/optional capabilities MAY be added). artifact.created.registeredis a new optional field, defaulttrue; existing clients ignore it, existing servers omit it — the default preserves today's "every artifactType accepted" semantics.- Free-string
artifactType/cardTyperemain valid (unregistered escape hatch). The RFC adds aSHOULDand a _conditional_MUST(validate iff registered and capability advertised); it relaxes no existingMUSTand rejects no input that previously succeeded (§4: new normative requirement on previously-undefined behavior = additive). PackNode.artifactgains prose, not shape.- §host.artifactTypes and §host.chat.cardPacks are off by default.
Spec minor bump; no major bump. No version-negotiation.md runbook needed (no negotiation-axis change).
Conformance
Existing adjacent: pack-registry-publish.test.ts, workflow-chain-pack-manifest-validation.test.ts, prompt-pack-install.test.ts, envelope-rendering-hint.test.ts (RFC 0055 closed display enum), workflow-primary-output-annotation.test.ts (RFC 0065).
New (Phase 1): artifact-type-pack-manifest-validation.test.ts (server-free: valid passes; mixed-kind rejected; core-scope-from-non-core rejected; unknown display rejected; empty artifactTypes rejected; <1s); artifact-type-pack-install.test.ts (publish→install→registered validates; unregistered accepted with registered:false; gated on host.artifactTypes.supported); artifact-type-store-without-render.test.ts (store:true,render:false stores and does not fail run).
New (Phase 2): chat-card-pack-manifest-validation.test.ts (closed inputs[].type enum; vendor.* tolerated); chat-card-pack-execution.test.ts (prompt routed through ctx.aiEnvelope.generate; output validated against linked outputArtifactType; gated on host.chat.cardPacks.supported).
All behavior scenarios use describe.skipIf(!driver.capabilities.<flag>) per conformance/coverage.md. New flags: host.artifactTypes.supported, host.chat.cardPacks.supported. Behavior grade starts host-pending. New fixtures conformance-artifact-type, conformance-chat-card + fixtures.md rows. INTEROP-MATRIX gains host.artifactTypes (store/render/export) + host.chat.cardPacks columns.
Alternatives considered
- Do nothing. Leave artifact/card typing host-private. _Rejected:_ leaves
PackNode.artifactcontract-free, guarantees any second artifact-producing host is non-interoperable with MyndHyve, and wastes the feature the sole adopter most visibly built on openwop. - Host-extension only (document
host.artifactTypesas a vendor capability, no pack kind, likehost.canvas). _Rejected:_ doesn't make schemas distributable/signed/verifiable, doesn't harden the free-string tag, leaves every host reinventing the registry, and leavesPackNode.artifactundefined. - One combined "canvas-type" pack porting MyndHyve's full manifest (node + card + canvas + viewers). _Rejected:_ violates
positioning.md("openwop is the wire contract … not a renderer"); imports UI-coupled single-adopter React/Fabric.js concepts; unbounded conformance burden. - Fold cards into the existing
promptpack kind. _Considered, deferred to Q4:_ prompt packs distribute reusable prompt fragments; cards are prompt + typed-output-artifact + input contract. Possibly unifiable, but conflating now risks overloadingprompt-pack-manifest.schema.json.
Resolved design decisions
The architect pass resolved all six original open questions, optimizing for low-friction adoption and consistency with existing corpus idioms. Each is now baked into the Phase 1 spec/schema:
- Schema home → in-tarball source of truth + optional host-served mirror. The schema travels in the signed tarball at
schemaRef(offline-verifiable, no host infra required to publish — matching node packs'*SchemaRef); a host that installs the pack and renders SHOULD _additionally_ serve it at{HostBase}/schemas/artifacts/{artifactTypeId}.schema.jsonfor runtime discovery by non-installing consumers (the envelope canonical-URL convention). Both copies MUST be byte-identical. Best of both: verifiable distribution _and_ frictionless resolution, neither forced. - Capability shape → single
host.artifactTypesobject withstore/render/export[]facets, not dotted sub-capabilities.exportcarries a list (impossible as a boolean dotted key), and the facets are negotiated together rather than dispatched as independent methods (the distinction that justifieshost.canvas.create's dotted form). - Unregistered types → permanent first-class tier. Free-string
artifactTypeis thelocal.*-scope analog and the adoption on-ramp;registered: falseis a permanent, honest signal, not a transitional wart. Registration governs whether a shape is bound to a distributed contract — never a precondition for producing artifacts. - Card kind → distinct
kind: "card". A prompt pack distributes reusable prompt fragments surfaced viaGET /v1/prompts; a card is a higher-order composite (input contract + prompt + output-artifact binding) consumed viaWorkflowNode.cardType/ctx.chat.emitCard. A card MAY _reference_ a prompt-pack template (composition over absorption), keeping the surfaces clean and DRY. - Versioning axis → integer
schemaVersionon the artifact type, riding the existingcapabilities.schemaVersions: Record<string, number>advertisement; the _pack_ keeps SemVer. This is the exact envelope split; semver minor/patch is meaningless for a wire schema. - **
exportFormats→ reserved core identifier set +vendor.*/x-extension.** A core vocabulary (pdf,pptx,docx,md,html,png,svg,csv,json,step,stl, …) carries interoperable meaning; domain formats extend via prefixed identifiers. Mirrors therequiredModelCapabilitiesidiom — interop without a codec spec.
Remaining open items (gate Phase-1 Active)
- R1 — bounded artifact-schema compilation. Distributed schemas are attacker-controllable input the engine compiles (Ajv). Before
Active: bound schema byte-size,$refdepth, and keyword count at registryPUTand at install; compile under a timeout; reject catastrophic-backtrackingpatterns. Land a protocol-tierartifact-schema-compile-boundedMUST-NOT inSECURITY/invariants.yaml+ a public conformance test (scripts/check-security-invariants.shgate). ExtendsSECURITY/threat-model-node-packs.md. - R2 — card-input trust boundary (Phase 2). Card
prompt.templateinterpolates workflow inputs; those deriveEnvelopeMeta.contentTrust: 'untrusted'and MUST propagate throughctx.aiEnvelope.generate. Document inSECURITY/threat-model-prompt-injection.md+ assert trust-tag propagation inchat-card-pack-execution. - G9 — portable card input-field subset. Confirm the
inputs[].typeclosed enum (text|longtext|number|boolean|select|artifact-ref) against MyndHyve's field types before Phase 2 schema freeze.
Implementation notes (non-normative)
- Sequencing: Phase 1 is independently shippable and should reach
Acceptedbefore Phase 2 (whoseoutputArtifactTypereferences Phase 1's registry)./plan 0071produced two milestone tracks. - Reuse, don't reinvent: registry
PUTalready validates per-kindand emitspack_kind_invalid; adding two kinds = two new manifest schemas + registry recognizing two newkindvalues.node-pack-manifest.schema.jsonis untouched. Signing (Ed25519) and naming are inherited. - Gate (
scripts/openwop-check.sh): new RFC + two spec docs + two schemas trigger step 7 (generate-protocol-status.mjs --check): updateREADME.mdcorpus + RFC counts, thennpm run protocol:status. Step 9 (check-security-invariants.sh) fires if a new MUST-NOT lands (see Risk R1). OpenAPI/AsyncAPI (steps 5–6) need the new manifest component(s). - Security: distributed artifact/output schemas are attacker-compilable input (Ajv) — bound size/
$ref-depth at registryPUT, add a protocol-tier MUST-NOT invariant + public test (R1). Card prompt templates are a prompt-injection surface — mandatecontentTrust='untrusted'propagation (R2). - Governance: a new normative pack kind merging under a single steward-maintainer (the non-steward tripwire has not fired —
GOVERNANCE.md,ROADMAP.md) warrants the full RFC comment window; solicit MyndHyve as external reviewer. - Reference consumers: demo app
chat/registry/CardHost.tsxand MyndHyve's runtime registries; neither has live card definitions yet, so Phase 2 defines the wire shape without breaking existing data.
Acceptance criteria
- [x] Spec text merged:
artifact-type-packs.md(FINAL),chat-card-packs.md(DRAFT, Phase 2), §host.artifactTypes + §host.chat.cardPacks,node-packs.mdPackNode.artifactprose. (#270) - [x] Schemas merged:
artifact-type-pack-manifest.schema.json,chat-card-pack-manifest.schema.json,run-event-payloads.schema.json(registered). (host.*capabilities advertise via the discovery open-set, notcapabilities.schema.json— seehost-capabilities.md§"The contract pattern"; no OpenAPI/AsyncAPI change — packs reuse the existing registry endpoints.) - [x] ≥1 conformance scenario per phase incl. the
store-without-rendernegotiation scenario. (4 Phase-1 + 2 Phase-2 scenarios) - [x] CHANGELOG entry under the spec-minor heading.
- [x] Phase 1: the non-steward MyndHyve
workflow-runtimehost implementshost.artifactTypesstore-side + passes the scenarios (steward production-curl-verified 2026-05-27) — Phase 1Accepted.
Remaining for the RFC overall (keeps Status: Active): Phase 2 (kind: "card") is now Active (G9 resolved 2026-05-27; R2 invariant + scenario landed). Phase 2 → Accepted awaits a host implementing host.chat.cardPacks and passing the chat-card-pack-execution.test.ts R2 trust-tag proof end-to-end. The RFC graduates to Accepted overall when both phases are Accepted.
References
- Spec docs:
spec/v1/node-packs.md§"Manifest format", §Naming;spec/v1/ai-envelope.md§"Rendering hints", §"Canonical schema location";spec/v1/host-capabilities.md§host.chat, §host.aiEnvelope, §"The contract pattern";spec/v1/capabilities.md§"Network-handshake shape"; newspec/v1/artifact-type-packs.md. - Schemas:
schemas/node-pack-manifest.schema.json(PackNode.artifact),schemas/workflow-definition.schema.json(artifactType,cardType),schemas/run-event-payloads.schema.json(artifactCreated),schemas/prompt-pack-manifest.schema.json(third-kind precedent), newschemas/artifact-type-pack-manifest.schema.json. - Related RFCs: RFC 0013 (workflow-chain packs — pack-kind precedent), RFC 0028 (prompt packs — third-kind precedent), RFC 0055 (
meta.rendering— vocabulary reused), RFC 0065 (outputRole), RFC 0066 (x-openwop-form— advisory-UI-hint precedent). - Prior art: MyndHyve
CanvasManifest/CardTemplateDefinition/ArtifactTypeDefinition; OpenWOP demo appchat/registry/CardHost.tsx; npm pack-kind / Helm chart-type discriminators; MCP resource typing. - Research:
docs/OPENWOP-CANVAS-TYPE-PACKS-RESEARCH.md. - Companion registers:
RFCS/registers/0071-artifact-type-and-chat-card-packs.gaps.md,RFCS/registers/0071-artifact-type-and-chat-card-packs.risks.md.