@openwop/openwop-conformance 1.58.0 → 1.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/coverage.md +24 -0
- package/package.json +1 -1
- package/schemas/README.md +1 -0
- package/schemas/artifact-type-pack-manifest.schema.json +108 -24
- package/schemas/chat-card-pack-manifest.schema.json +125 -30
- package/schemas/connection-pack-manifest.schema.json +211 -38
- package/schemas/form-content-pack-manifest.schema.json +243 -0
- package/schemas/frontend-plugin-manifest.schema.json +10 -0
- package/schemas/node-pack-manifest.schema.json +311 -68
- package/schemas/prompt-pack-manifest.schema.json +48 -11
- package/schemas/registry-version-manifest.schema.json +16 -5
- package/schemas/workflow-chain-pack-manifest.schema.json +176 -40
- package/src/lib/artifactTypes.ts +76 -3
- package/src/lib/behavior-gate.ts +17 -0
- package/src/lib/cardPacks.ts +10 -3
- package/src/lib/formContentPacks.ts +133 -0
- package/src/lib/workflow-chain-expansion.ts +27 -0
- package/src/scenarios/artifact-type-pack-install.test.ts +17 -6
- package/src/scenarios/artifact-type-store-without-render.test.ts +17 -4
- package/src/scenarios/chat-card-pack-execution.test.ts +17 -5
- package/src/scenarios/form-content-instantiation.test.ts +168 -0
- package/src/scenarios/form-content-packs.test.ts +415 -0
- package/src/scenarios/pack-manifest-extension-opacity.test.ts +179 -0
- package/src/scenarios/pack-manifest-extensions.test.ts +203 -0
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
"title": "ConnectionPackManifest",
|
|
5
5
|
"description": "RFC 0095. Manifest for a published OpenWOP connection pack — `pack.json` at the pack root with `kind: \"connection\"`. Peer to and disjoint from `node-pack-manifest.schema.json` (RFC 0003), `prompt-pack-manifest.schema.json` (RFC 0028), and `workflow-chain-pack-manifest.schema.json` (RFC 0013) via the `kind` discriminator. A connection pack is a portable PROVIDER definition — the authorize/token/revoke endpoints, the read/write OAuth scope catalog, and how the integration is reached (an MCP server, an OpenAPI surface, or a core integration node). It carries NO secret: the OAuth client credential is a host concern (RFC 0047), supplied out of band. When installed, it makes an RFC 0045 connector's `auth.provider` / an RFC 0047 `host.oauth` provider string resolve against `provider.id` instead of host-locked code. See `spec/v1/connection-packs.md`.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": [
|
|
7
|
+
"required": [
|
|
8
|
+
"name",
|
|
9
|
+
"version",
|
|
10
|
+
"kind",
|
|
11
|
+
"engines",
|
|
12
|
+
"provider"
|
|
13
|
+
],
|
|
8
14
|
"additionalProperties": false,
|
|
9
15
|
"properties": {
|
|
10
16
|
"name": {
|
|
@@ -24,34 +30,77 @@
|
|
|
24
30
|
"const": "connection",
|
|
25
31
|
"description": "Pack kind discriminator. MUST be the literal string `\"connection\"` for this schema."
|
|
26
32
|
},
|
|
27
|
-
"description": {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
33
|
+
"description": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"maxLength": 1024
|
|
36
|
+
},
|
|
37
|
+
"author": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"license": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "SPDX license identifier (e.g., `Apache-2.0`)."
|
|
43
|
+
},
|
|
44
|
+
"homepage": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"format": "uri"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"format": "uri"
|
|
51
|
+
},
|
|
32
52
|
"keywords": {
|
|
33
53
|
"type": "array",
|
|
34
|
-
"items": {
|
|
54
|
+
"items": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"maxLength": 64
|
|
57
|
+
},
|
|
35
58
|
"maxItems": 50
|
|
36
59
|
},
|
|
37
60
|
"engines": {
|
|
38
61
|
"type": "object",
|
|
39
|
-
"required": [
|
|
62
|
+
"required": [
|
|
63
|
+
"openwop"
|
|
64
|
+
],
|
|
40
65
|
"additionalProperties": false,
|
|
41
66
|
"properties": {
|
|
42
|
-
"openwop": {
|
|
67
|
+
"openwop": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "SemVer range of the openwop spec version this pack targets."
|
|
70
|
+
}
|
|
43
71
|
}
|
|
44
72
|
},
|
|
45
73
|
"provider": {
|
|
46
74
|
"type": "object",
|
|
47
75
|
"description": "The portable provider definition. Exactly one per connection pack.",
|
|
48
|
-
"required": [
|
|
76
|
+
"required": [
|
|
77
|
+
"id",
|
|
78
|
+
"displayName",
|
|
79
|
+
"category",
|
|
80
|
+
"auth",
|
|
81
|
+
"reach"
|
|
82
|
+
],
|
|
49
83
|
"additionalProperties": false,
|
|
50
84
|
"allOf": [
|
|
51
85
|
{
|
|
52
86
|
"$comment": "RFC 0120 UQ1: apiHosts is REQUIRED when reach is openapi (a credentialed REST/OpenAPI egress surface). A pure metadata / mcp / integration provider MAY omit it. The runtime binding-site validator (spec §Manifest item 13) is the complementary check for credentialed-egress paths the manifest cannot see.",
|
|
53
|
-
"if": {
|
|
54
|
-
|
|
87
|
+
"if": {
|
|
88
|
+
"required": [
|
|
89
|
+
"reach"
|
|
90
|
+
],
|
|
91
|
+
"properties": {
|
|
92
|
+
"reach": {
|
|
93
|
+
"required": [
|
|
94
|
+
"openapi"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"then": {
|
|
100
|
+
"required": [
|
|
101
|
+
"apiHosts"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
55
104
|
}
|
|
56
105
|
],
|
|
57
106
|
"properties": {
|
|
@@ -60,10 +109,30 @@
|
|
|
60
109
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
61
110
|
"description": "Stable provider id an RFC 0045/0047 `provider` string resolves to, e.g. `github`."
|
|
62
111
|
},
|
|
63
|
-
"displayName": {
|
|
112
|
+
"displayName": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"minLength": 1
|
|
115
|
+
},
|
|
64
116
|
"category": {
|
|
65
117
|
"type": "string",
|
|
66
|
-
"enum": [
|
|
118
|
+
"enum": [
|
|
119
|
+
"communication",
|
|
120
|
+
"docs",
|
|
121
|
+
"crm",
|
|
122
|
+
"dev",
|
|
123
|
+
"storage",
|
|
124
|
+
"email-calendar",
|
|
125
|
+
"ticketing",
|
|
126
|
+
"data-warehouse",
|
|
127
|
+
"marketing",
|
|
128
|
+
"finance",
|
|
129
|
+
"hr",
|
|
130
|
+
"esignature",
|
|
131
|
+
"support",
|
|
132
|
+
"project-management",
|
|
133
|
+
"payments",
|
|
134
|
+
"other"
|
|
135
|
+
]
|
|
67
136
|
},
|
|
68
137
|
"vendor": {
|
|
69
138
|
"type": "string",
|
|
@@ -72,14 +141,36 @@
|
|
|
72
141
|
},
|
|
73
142
|
"auth": {
|
|
74
143
|
"type": "object",
|
|
75
|
-
"required": [
|
|
144
|
+
"required": [
|
|
145
|
+
"kind"
|
|
146
|
+
],
|
|
76
147
|
"additionalProperties": false,
|
|
77
148
|
"properties": {
|
|
78
|
-
"kind": {
|
|
79
|
-
|
|
149
|
+
"kind": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"enum": [
|
|
152
|
+
"oauth2",
|
|
153
|
+
"api_key",
|
|
154
|
+
"bearer",
|
|
155
|
+
"basic"
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
"authFlow": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"enum": [
|
|
161
|
+
"pkce",
|
|
162
|
+
"client_credentials",
|
|
163
|
+
"manual",
|
|
164
|
+
"none"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
80
167
|
"scopeModel": {
|
|
81
168
|
"type": "string",
|
|
82
|
-
"enum": [
|
|
169
|
+
"enum": [
|
|
170
|
+
"groups",
|
|
171
|
+
"coarse",
|
|
172
|
+
"capabilities"
|
|
173
|
+
],
|
|
83
174
|
"default": "groups",
|
|
84
175
|
"description": "`groups` = read/write scope groups (Google/Jira/Graph). `coarse` = a single read-vs-write toggle (Stripe Connect read_only/read_write). `capabilities` = consent is a static developer-portal capability set, not per-request scopes (Notion)."
|
|
85
176
|
},
|
|
@@ -88,9 +179,21 @@
|
|
|
88
179
|
"additionalProperties": false,
|
|
89
180
|
"description": "Fixed, manifest-declared https endpoints (RFC 0095 §B.3). Never derived from runtime user input — not an SSRF surface.",
|
|
90
181
|
"properties": {
|
|
91
|
-
"authorize": {
|
|
92
|
-
|
|
93
|
-
|
|
182
|
+
"authorize": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"format": "uri",
|
|
185
|
+
"pattern": "^https://"
|
|
186
|
+
},
|
|
187
|
+
"token": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"format": "uri",
|
|
190
|
+
"pattern": "^https://"
|
|
191
|
+
},
|
|
192
|
+
"revoke": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"format": "uri",
|
|
195
|
+
"pattern": "^https://"
|
|
196
|
+
}
|
|
94
197
|
}
|
|
95
198
|
},
|
|
96
199
|
"scopes": {
|
|
@@ -98,8 +201,18 @@
|
|
|
98
201
|
"additionalProperties": false,
|
|
99
202
|
"description": "Read/write scope groups. `write` is requested as a SEPARATE re-consent step (RFC 0095 §B.4).",
|
|
100
203
|
"properties": {
|
|
101
|
-
"read": {
|
|
102
|
-
|
|
204
|
+
"read": {
|
|
205
|
+
"type": "array",
|
|
206
|
+
"items": {
|
|
207
|
+
"$ref": "#/$defs/ScopeGroup"
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"write": {
|
|
211
|
+
"type": "array",
|
|
212
|
+
"items": {
|
|
213
|
+
"$ref": "#/$defs/ScopeGroup"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
103
216
|
}
|
|
104
217
|
},
|
|
105
218
|
"instanceUrlTemplate": {
|
|
@@ -117,64 +230,124 @@
|
|
|
117
230
|
"properties": {
|
|
118
231
|
"mcp": {
|
|
119
232
|
"type": "object",
|
|
120
|
-
"required": [
|
|
233
|
+
"required": [
|
|
234
|
+
"server"
|
|
235
|
+
],
|
|
121
236
|
"additionalProperties": false,
|
|
122
237
|
"properties": {
|
|
123
238
|
"server": {
|
|
124
239
|
"type": "object",
|
|
125
|
-
"required": [
|
|
240
|
+
"required": [
|
|
241
|
+
"url",
|
|
242
|
+
"transport"
|
|
243
|
+
],
|
|
126
244
|
"additionalProperties": false,
|
|
127
245
|
"properties": {
|
|
128
|
-
"url": {
|
|
129
|
-
|
|
246
|
+
"url": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"format": "uri",
|
|
249
|
+
"pattern": "^https://"
|
|
250
|
+
},
|
|
251
|
+
"transport": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"enum": [
|
|
254
|
+
"http",
|
|
255
|
+
"sse"
|
|
256
|
+
]
|
|
257
|
+
}
|
|
130
258
|
}
|
|
131
259
|
}
|
|
132
260
|
}
|
|
133
261
|
},
|
|
134
262
|
"openapi": {
|
|
135
263
|
"type": "object",
|
|
136
|
-
"required": [
|
|
264
|
+
"required": [
|
|
265
|
+
"ref"
|
|
266
|
+
],
|
|
137
267
|
"additionalProperties": false,
|
|
138
268
|
"properties": {
|
|
139
|
-
"ref": {
|
|
269
|
+
"ref": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"description": "URL or in-pack path of the OpenAPI document for core.openwop.http.openapi-call."
|
|
272
|
+
}
|
|
140
273
|
}
|
|
141
274
|
},
|
|
142
275
|
"integration": {
|
|
143
276
|
"type": "object",
|
|
144
|
-
"required": [
|
|
277
|
+
"required": [
|
|
278
|
+
"node"
|
|
279
|
+
],
|
|
145
280
|
"additionalProperties": false,
|
|
146
281
|
"properties": {
|
|
147
|
-
"node": {
|
|
282
|
+
"node": {
|
|
283
|
+
"type": "string",
|
|
284
|
+
"description": "A core.openwop.integration.* node typeId."
|
|
285
|
+
}
|
|
148
286
|
}
|
|
149
287
|
}
|
|
150
288
|
}
|
|
151
289
|
},
|
|
152
290
|
"consumerNodes": {
|
|
153
291
|
"type": "array",
|
|
154
|
-
"items": {
|
|
292
|
+
"items": {
|
|
293
|
+
"type": "string"
|
|
294
|
+
},
|
|
155
295
|
"description": "The core node typeIds that consume this provider's credential (e.g. core.openwop.mcp.invoke-tool)."
|
|
156
296
|
},
|
|
157
|
-
"docsUrl": {
|
|
297
|
+
"docsUrl": {
|
|
298
|
+
"type": "string",
|
|
299
|
+
"format": "uri"
|
|
300
|
+
},
|
|
158
301
|
"apiHosts": {
|
|
159
302
|
"type": "array",
|
|
160
303
|
"description": "RFC 0120. API host(s) a host MAY send this provider's resolved credential to for connector egress (RFC 0045). Each entry is a bare registrable hostname — lowercase ASCII, >=2 labels, TLD label beginning with a letter; no scheme, port, path, wildcard, or IP literal. Matched by dot-anchored suffix containment (request host == entry OR ends with '.'+entry); eTLD+1 is the floor and a pack MAY list a tighter exact host (e.g. googleads.googleapis.com). Omitted => no credential-bearing connector egress is reachable (fails closed). REQUIRED when reach is openapi (see the provider-level allOf).",
|
|
161
|
-
"items": {
|
|
304
|
+
"items": {
|
|
305
|
+
"type": "string",
|
|
306
|
+
"format": "hostname",
|
|
307
|
+
"pattern": "^(?!-)[a-z0-9-]{1,63}(\\.[a-z0-9-]{1,63})*\\.[a-z][a-z0-9-]*$"
|
|
308
|
+
},
|
|
162
309
|
"uniqueItems": true,
|
|
163
310
|
"minItems": 1
|
|
164
311
|
}
|
|
312
|
+
},
|
|
313
|
+
"patternProperties": {
|
|
314
|
+
"^(x-|vendor\\.)": {
|
|
315
|
+
"description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` §\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque — a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` §\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
|
|
316
|
+
}
|
|
165
317
|
}
|
|
166
318
|
}
|
|
167
319
|
},
|
|
168
320
|
"$defs": {
|
|
169
321
|
"ScopeGroup": {
|
|
170
322
|
"type": "object",
|
|
171
|
-
"required": [
|
|
323
|
+
"required": [
|
|
324
|
+
"key",
|
|
325
|
+
"label",
|
|
326
|
+
"scopes"
|
|
327
|
+
],
|
|
172
328
|
"additionalProperties": false,
|
|
173
329
|
"properties": {
|
|
174
|
-
"key": {
|
|
175
|
-
|
|
176
|
-
|
|
330
|
+
"key": {
|
|
331
|
+
"type": "string",
|
|
332
|
+
"pattern": "^[a-z][a-z0-9._-]*$"
|
|
333
|
+
},
|
|
334
|
+
"label": {
|
|
335
|
+
"type": "string",
|
|
336
|
+
"minLength": 1
|
|
337
|
+
},
|
|
338
|
+
"scopes": {
|
|
339
|
+
"type": "array",
|
|
340
|
+
"items": {
|
|
341
|
+
"type": "string"
|
|
342
|
+
},
|
|
343
|
+
"description": "The provider's raw scope strings, e.g. `https://www.googleapis.com/auth/drive.readonly`."
|
|
344
|
+
}
|
|
177
345
|
}
|
|
178
346
|
}
|
|
347
|
+
},
|
|
348
|
+
"patternProperties": {
|
|
349
|
+
"^(x-|vendor\\.)": {
|
|
350
|
+
"description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` §\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque — a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` §\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
|
|
351
|
+
}
|
|
179
352
|
}
|
|
180
353
|
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/form-content-pack-manifest.schema.json",
|
|
4
|
+
"title": "FormContentPackManifest",
|
|
5
|
+
"description": "Manifest for a published OpenWOP form-content pack — `pack.json` at the pack root with `kind: \"form-content\"`. Peer to the node / workflow-chain / prompt / artifact-type / card pack manifests; disjoint via the `kind` discriminator. See `spec/v1/form-content-packs.md` for the canonical contract and RFC 0137 for the rationale.\n\nA form-content pack distributes **form templates**: a named, versioned set of typed input fields a host instantiates into an ordinary, fully editable form through its normal create path. It is purely declarative — no `runtime`, no executor bytecode, no entry point. The pack contributes the *shape* of a form; it does not define a submission surface, a handler, or any behavior the host would not otherwise accept.\n\n**A template carries NO submission routing, and MUST NOT gain any.** There is deliberately no `intakeBinding` / destination / webhook / list-id property on `FormTemplate`, and `additionalProperties: false` rejects one. Where a tenant's form submissions go is a decision about the OPERATOR's data; a third-party pack author does not get to make it by shipping a manifest. Enforced as the `form-content-template-no-submission-routing` SECURITY invariant — see `form-content-packs.md` §\"No submission routing\".\n\n**The wire vocabulary governs the WIRE, not host storage.** A host MAY store fields under any internal representation it likes and translate at the pack boundary; the spec constrains what crosses the wire, not what sits in a host's database (`positioning.md`).\n\n**Field types are the RFC 0071 portable subset, not a second vocabulary.** `fields[].type` reuses the closed portable subset defined in `chat-card-packs.md` §\"Input fields — a closed portable subset\" (RFC 0071 Phase 2, gap G9 resolved 2026-05-27) verbatim, including its `vendor.<org>.<kind>` / `x-<kind>` extension escape hatch. Two declarative pack kinds that both collect typed user input MUST agree on what a field type means.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"name",
|
|
9
|
+
"version",
|
|
10
|
+
"kind",
|
|
11
|
+
"engines",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"properties": {
|
|
16
|
+
"kind": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"const": "form-content",
|
|
19
|
+
"description": "Pack kind discriminator. MUST be the literal string `\"form-content\"`."
|
|
20
|
+
},
|
|
21
|
+
"name": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Reverse-DNS pack name per `node-packs.md` §Naming. Reserved scopes are identical (`core.*` / `vendor.<org>.*` / `community.<author>.*` / `private.<host>.*`).",
|
|
24
|
+
"pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
|
|
25
|
+
"minLength": 1,
|
|
26
|
+
"maxLength": 256
|
|
27
|
+
},
|
|
28
|
+
"version": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Pack-level SemVer 2.0.0.",
|
|
31
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$"
|
|
32
|
+
},
|
|
33
|
+
"description": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"maxLength": 1024
|
|
36
|
+
},
|
|
37
|
+
"author": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"license": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "SPDX license identifier (e.g., `Apache-2.0`, `MIT`)."
|
|
43
|
+
},
|
|
44
|
+
"homepage": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"format": "uri"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"format": "uri"
|
|
51
|
+
},
|
|
52
|
+
"keywords": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"maxLength": 64
|
|
57
|
+
},
|
|
58
|
+
"maxItems": 50
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"required": [
|
|
63
|
+
"openwop"
|
|
64
|
+
],
|
|
65
|
+
"properties": {
|
|
66
|
+
"openwop": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Semver range — which openwop protocol versions this pack works against."
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"additionalProperties": true
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": {
|
|
76
|
+
"type": "string"
|
|
77
|
+
},
|
|
78
|
+
"description": "Other packs this pack depends on. Map of pack name → semver range."
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"additionalProperties": {
|
|
83
|
+
"type": "string"
|
|
84
|
+
},
|
|
85
|
+
"description": "Engine-supplied capabilities the pack consumes (e.g., `{ \"host.forms.contentPacks\": \"supported\" }`). Resolved against the host's advertised capabilities at register time."
|
|
86
|
+
},
|
|
87
|
+
"templates": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"minItems": 1,
|
|
90
|
+
"maxItems": 100,
|
|
91
|
+
"items": {
|
|
92
|
+
"$ref": "#/$defs/FormTemplate"
|
|
93
|
+
},
|
|
94
|
+
"description": "Form template definitions this pack contributes. Each MUST have a unique `templateId` within the pack — duplicates MUST be refused at registry `PUT` / install (`form-content-packs.md` §\"Unique identifiers\"); JSON Schema cannot express uniqueness-by-property, so this is a normative prose requirement, as it is for every peer pack kind. `maxItems` is an OUTER resource bound against a render bomb, not product policy — a host MAY enforce a tighter limit locally."
|
|
95
|
+
},
|
|
96
|
+
"signing": {
|
|
97
|
+
"$ref": "#/$defs/Signing"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"$defs": {
|
|
101
|
+
"FormTemplate": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"required": [
|
|
104
|
+
"templateId",
|
|
105
|
+
"version",
|
|
106
|
+
"label",
|
|
107
|
+
"title",
|
|
108
|
+
"fields"
|
|
109
|
+
],
|
|
110
|
+
"additionalProperties": false,
|
|
111
|
+
"properties": {
|
|
112
|
+
"templateId": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"description": "Reverse-DNS template identifier. Same pattern and reserved scopes as a pack `name`. This is the id a registry denormalizes for discovery (`registry-operations.md` §\"Type-ID indexing\") and the id a host resolves when instantiating. Third parties MUST NOT publish under `core.*`.",
|
|
115
|
+
"pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
|
|
116
|
+
"minLength": 1,
|
|
117
|
+
"maxLength": 256
|
|
118
|
+
},
|
|
119
|
+
"version": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Per-template SemVer 2.0.0 — the SAME axis as the pack `version`, NOT the non-negative-integer schema-version axis used by `chat-card-pack-manifest.schema.json` `schemaVersion` / `artifact-type-pack-manifest.schema.json` `schemaVersion`. A form template is a distributable artifact whose shape changes are author-meaningful (a removed field is a breaking change to anything storing its submissions), so it carries the same SemVer contract as the pack.",
|
|
122
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$"
|
|
123
|
+
},
|
|
124
|
+
"label": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"minLength": 1,
|
|
127
|
+
"maxLength": 200,
|
|
128
|
+
"description": "Short human-readable name for a picker / gallery listing. UNTRUSTED pack-authored content — see `form-content-packs.md` §\"Trust boundary\". The length bound is a resource guard, NOT a trust boundary."
|
|
129
|
+
},
|
|
130
|
+
"title": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"minLength": 1,
|
|
133
|
+
"maxLength": 200,
|
|
134
|
+
"description": "Heading rendered above the instantiated form. UNTRUSTED pack-authored content — see `form-content-packs.md` §\"Trust boundary\"."
|
|
135
|
+
},
|
|
136
|
+
"description": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"maxLength": 300,
|
|
139
|
+
"description": "Optional explanatory copy. UNTRUSTED pack-authored content — see `form-content-packs.md` §\"Trust boundary\"."
|
|
140
|
+
},
|
|
141
|
+
"category": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"maxLength": 64,
|
|
144
|
+
"description": "Optional grouping hint for a picker. Advisory — the spec assigns no meaning to any particular value and a host MUST NOT treat it as an authorization or visibility boundary."
|
|
145
|
+
},
|
|
146
|
+
"fields": {
|
|
147
|
+
"type": "array",
|
|
148
|
+
"minItems": 1,
|
|
149
|
+
"maxItems": 200,
|
|
150
|
+
"items": {
|
|
151
|
+
"$ref": "#/$defs/FormField"
|
|
152
|
+
},
|
|
153
|
+
"description": "Typed fields the template contributes, in presentation order. Each MUST have a unique `id` within the template — duplicates MUST be refused (`form-content-packs.md` §\"Unique identifiers\"). Two fields sharing an `id` silently overwrite one another in the submission value bag, losing data with no error, so this is a data-integrity requirement rather than a style rule. `maxItems` is an OUTER resource bound; a host MAY enforce a tighter limit locally."
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"patternProperties": {
|
|
157
|
+
"^(x-|vendor\\.)": {
|
|
158
|
+
"description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` §\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque — a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` §\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"FormField": {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"required": [
|
|
165
|
+
"id",
|
|
166
|
+
"type",
|
|
167
|
+
"label"
|
|
168
|
+
],
|
|
169
|
+
"additionalProperties": false,
|
|
170
|
+
"description": "One typed field. `id` (not `key`) matches `chat-card-pack-manifest.schema.json#/$defs/InputField` so a host renders both declarative kinds through the same machinery.",
|
|
171
|
+
"properties": {
|
|
172
|
+
"id": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"minLength": 1,
|
|
175
|
+
"pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$",
|
|
176
|
+
"description": "Field identifier, unique within the template. Mirrors `chat-card-pack-manifest.schema.json` `InputField.id`."
|
|
177
|
+
},
|
|
178
|
+
"type": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"description": "Closed portable subset OR a `vendor.<org>.<kind>` / `x-<kind>` host extension other hosts MUST ignore (degrading to a plain text input). The subset is IDENTICAL to `chat-card-pack-manifest.schema.json` `InputField.type` (RFC 0071 G9, resolved 2026-05-27): `text`, `longtext`, `number`, `boolean`, `select`, `multiselect`, `file`, `artifact-ref`. Named by DATA KIND, not widget — a host renders `boolean` as whatever toggle/checkbox it likes, and `longtext` as whatever multi-line control it likes. Validation formats (`email`, `uri`, …) are NOT types; use `format`.",
|
|
181
|
+
"pattern": "^(text|longtext|number|boolean|select|multiselect|file|artifact-ref|vendor\\.[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*|x-[a-z][a-z0-9-]*)$"
|
|
182
|
+
},
|
|
183
|
+
"format": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"description": "Optional validation-format constraint on the field's value, following the JSON Schema `format` idiom used throughout this corpus. Spec-reserved core identifiers: `email`, `uri`, `date`, `date-time`, `time`. Host extensions MUST be `x-<format>`-prefixed. Only meaningful for `type: \"text\"` / `\"longtext\"`; a host MUST ignore it on any other type. A host that recognizes the format SHOULD apply it as an input-validation constraint; a host that does NOT recognize it MUST ignore it and accept the value as plain text (the same degradation contract as an unrecognized `type`). `format` is a CONSTRAINT on a `text` field — it is deliberately not a member of the `type` vocabulary, so the portable type set stays a stable set of data kinds instead of accreting widgets.",
|
|
186
|
+
"pattern": "^(email|uri|date|date-time|time|x-[a-z][a-z0-9-]*)$"
|
|
187
|
+
},
|
|
188
|
+
"label": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"minLength": 1,
|
|
191
|
+
"maxLength": 1000,
|
|
192
|
+
"description": "Human-readable field label. Bounded at 1000 rather than 200 because a lawful consent label (GDPR/marketing opt-in) is legitimately long-form. UNTRUSTED pack-authored content — see `form-content-packs.md` §\"Trust boundary\"."
|
|
193
|
+
},
|
|
194
|
+
"description": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"maxLength": 300,
|
|
197
|
+
"description": "Optional help text. UNTRUSTED pack-authored content — see `form-content-packs.md` §\"Trust boundary\"."
|
|
198
|
+
},
|
|
199
|
+
"required": {
|
|
200
|
+
"type": "boolean",
|
|
201
|
+
"description": "Whether the host SHOULD require a value before accepting the instantiated form. Absent ⇒ `false`."
|
|
202
|
+
},
|
|
203
|
+
"default": {
|
|
204
|
+
"description": "Optional default value. Its JSON type SHOULD match the field `type` (string for `text`/`longtext`, number for `number`, boolean for `boolean`, string for `select`, array of string for `multiselect`); a host MUST ignore a default whose type does not match rather than fail the instantiation."
|
|
205
|
+
},
|
|
206
|
+
"options": {
|
|
207
|
+
"type": "array",
|
|
208
|
+
"items": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"maxLength": 200
|
|
211
|
+
},
|
|
212
|
+
"maxItems": 250,
|
|
213
|
+
"description": "Choices for `type: \"select\"` / `\"multiselect\"`. UNTRUSTED pack-authored content — see `form-content-packs.md` §\"Trust boundary\". Mirrors `chat-card-pack-manifest.schema.json` `InputField.options`."
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"Signing": {
|
|
218
|
+
"type": "object",
|
|
219
|
+
"description": "Optional signing metadata. See node-packs.md §signing.",
|
|
220
|
+
"additionalProperties": false,
|
|
221
|
+
"properties": {
|
|
222
|
+
"publicKeyRef": {
|
|
223
|
+
"type": "string"
|
|
224
|
+
},
|
|
225
|
+
"signatureRef": {
|
|
226
|
+
"type": "string"
|
|
227
|
+
},
|
|
228
|
+
"method": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"enum": [
|
|
231
|
+
"manual",
|
|
232
|
+
"sigstore"
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"patternProperties": {
|
|
239
|
+
"^(x-|vendor\\.)": {
|
|
240
|
+
"description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` §\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque — a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` §\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"items": { "$ref": "#/$defs/uiPlugin" }
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
+
"patternProperties": {
|
|
55
|
+
"^(x-|vendor\\.)": {
|
|
56
|
+
"description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` \u00a7\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque \u2014 a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` \u00a7\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
54
59
|
"$defs": {
|
|
55
60
|
"uiPlugin": {
|
|
56
61
|
"type": "object",
|
|
@@ -93,6 +98,11 @@
|
|
|
93
98
|
"items": { "type": "string", "format": "uri" },
|
|
94
99
|
"uniqueItems": true
|
|
95
100
|
}
|
|
101
|
+
},
|
|
102
|
+
"patternProperties": {
|
|
103
|
+
"^(x-|vendor\\.)": {
|
|
104
|
+
"description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` \u00a7\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque \u2014 a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` \u00a7\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
|
|
105
|
+
}
|
|
96
106
|
}
|
|
97
107
|
}
|
|
98
108
|
}
|