@openwop/spec-artifacts 2.0.0-rc.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.
Files changed (229) hide show
  1. package/CORPUS-STAMP.json +234 -0
  2. package/LICENSE +201 -0
  3. package/README.md +7 -0
  4. package/api/.redocly.lint-ignore.yaml +22 -0
  5. package/api/asyncapi.yaml +834 -0
  6. package/api/grpc/openwop.proto +251 -0
  7. package/api/openapi.yaml +2693 -0
  8. package/api/redocly.yaml +23 -0
  9. package/api/seams-v2.yaml +680 -0
  10. package/api/v2/asyncapi.yaml +528 -0
  11. package/api/v2/openapi.yaml +3139 -0
  12. package/api/v2/redocly.yaml +16 -0
  13. package/package.json +10 -0
  14. package/schemas/README.md +144 -0
  15. package/schemas/a2a-task-state.schema.json +78 -0
  16. package/schemas/a2ui-surface-delta-frame.schema.json +48 -0
  17. package/schemas/agent-deployment-transition.schema.json +49 -0
  18. package/schemas/agent-deployment.schema.json +54 -0
  19. package/schemas/agent-eval-suite.schema.json +140 -0
  20. package/schemas/agent-inventory-response.schema.json +115 -0
  21. package/schemas/agent-manifest.schema.json +156 -0
  22. package/schemas/agent-org-chart.schema.json +82 -0
  23. package/schemas/agent-ref.schema.json +63 -0
  24. package/schemas/agent-roster-entry.schema.json +81 -0
  25. package/schemas/agent-roster-response.schema.json +21 -0
  26. package/schemas/ai-envelope.schema.json +134 -0
  27. package/schemas/annotation-create.schema.json +37 -0
  28. package/schemas/annotation.schema.json +56 -0
  29. package/schemas/artifact-type-pack-manifest.schema.json +244 -0
  30. package/schemas/audit-verify-result.schema.json +90 -0
  31. package/schemas/budget-policy.schema.json +18 -0
  32. package/schemas/capabilities.schema.json +4160 -0
  33. package/schemas/certification-bundle-v2.schema.json +118 -0
  34. package/schemas/channel-presence-payload.schema.json +41 -0
  35. package/schemas/channel-written-payload.schema.json +55 -0
  36. package/schemas/chat-card-pack-manifest.schema.json +253 -0
  37. package/schemas/compact-tool-descriptor.schema.json +51 -0
  38. package/schemas/compensation-policy.schema.json +64 -0
  39. package/schemas/conformance-certification-bundle.schema.json +86 -0
  40. package/schemas/connection-pack-manifest.schema.json +353 -0
  41. package/schemas/conversation-event.schema.json +168 -0
  42. package/schemas/conversation-turn.schema.json +117 -0
  43. package/schemas/core-conformance-mock-agent-config.schema.json +152 -0
  44. package/schemas/credential-provenance.schema.json +18 -0
  45. package/schemas/credential-reference.schema.json +21 -0
  46. package/schemas/debug-bundle.schema.json +196 -0
  47. package/schemas/dispatch-config.schema.json +112 -0
  48. package/schemas/envelopes/clarification.request.schema.json +52 -0
  49. package/schemas/envelopes/error.schema.json +30 -0
  50. package/schemas/envelopes/media.audio.schema.json +38 -0
  51. package/schemas/envelopes/media.file.schema.json +37 -0
  52. package/schemas/envelopes/media.image.schema.json +33 -0
  53. package/schemas/envelopes/schema.request.schema.json +26 -0
  54. package/schemas/envelopes/schema.response.schema.json +22 -0
  55. package/schemas/envelopes/ui.a2ui-surface.schema.json +154 -0
  56. package/schemas/error-envelope.schema.json +25 -0
  57. package/schemas/eval-summary.schema.json +92 -0
  58. package/schemas/export-bundle.schema.json +66 -0
  59. package/schemas/form-content-pack-manifest.schema.json +243 -0
  60. package/schemas/frontend-plugin-manifest.schema.json +109 -0
  61. package/schemas/goal.schema.json +104 -0
  62. package/schemas/heartbeat-evaluated.schema.json +14 -0
  63. package/schemas/heartbeat-state-changed.schema.json +14 -0
  64. package/schemas/localized-content-language-settings.schema.json +26 -0
  65. package/schemas/localized-content-page-response.schema.json +60 -0
  66. package/schemas/localized-content-page.schema.json +62 -0
  67. package/schemas/localized-content-section.schema.json +51 -0
  68. package/schemas/memory-entry.schema.json +36 -0
  69. package/schemas/memory-list-options.schema.json +37 -0
  70. package/schemas/node-pack-manifest.schema.json +654 -0
  71. package/schemas/orchestrator-decision.schema.json +90 -0
  72. package/schemas/org-chart-responsibility-view.schema.json +26 -0
  73. package/schemas/pack-lockfile.schema.json +108 -0
  74. package/schemas/prompt-kind.schema.json +8 -0
  75. package/schemas/prompt-pack-manifest.schema.json +117 -0
  76. package/schemas/prompt-ref.schema.json +40 -0
  77. package/schemas/prompt-template.schema.json +149 -0
  78. package/schemas/proposal.schema.json +84 -0
  79. package/schemas/registry-version-manifest.schema.json +208 -0
  80. package/schemas/residency.schema.json +16 -0
  81. package/schemas/run-ancestry-response.schema.json +54 -0
  82. package/schemas/run-diff-response.schema.json +64 -0
  83. package/schemas/run-event-payloads.schema.json +4452 -0
  84. package/schemas/run-event.schema.json +211 -0
  85. package/schemas/run-options.schema.json +80 -0
  86. package/schemas/run-orchestrator-decided-event.schema.json +20 -0
  87. package/schemas/run-snapshot.schema.json +148 -0
  88. package/schemas/security-advisory.schema.json +109 -0
  89. package/schemas/self-hosted-runner-dispatch-frame.schema.json +79 -0
  90. package/schemas/self-hosted-runner-registration.schema.json +53 -0
  91. package/schemas/self-hosted-runner-result-frame.schema.json +38 -0
  92. package/schemas/subject.schema.json +56 -0
  93. package/schemas/suspend-request.schema.json +207 -0
  94. package/schemas/tool-descriptor.schema.json +63 -0
  95. package/schemas/trigger-event.schema.json +222 -0
  96. package/schemas/trigger-subscription-registration.schema.json +67 -0
  97. package/schemas/trigger-subscription.schema.json +26 -0
  98. package/schemas/ui-plugin-message.schema.json +90 -0
  99. package/schemas/v2/a2a-task-state.schema.json +102 -0
  100. package/schemas/v2/a2ui-surface-delta-frame.schema.json +62 -0
  101. package/schemas/v2/agent-deployment-transition.schema.json +67 -0
  102. package/schemas/v2/agent-deployment.schema.json +68 -0
  103. package/schemas/v2/agent-eval-suite.schema.json +214 -0
  104. package/schemas/v2/agent-inventory-response.schema.json +172 -0
  105. package/schemas/v2/agent-manifest.schema.json +218 -0
  106. package/schemas/v2/agent-org-chart.schema.json +149 -0
  107. package/schemas/v2/agent-ref.schema.json +67 -0
  108. package/schemas/v2/agent-roster-entry.schema.json +104 -0
  109. package/schemas/v2/agent-roster-response.schema.json +27 -0
  110. package/schemas/v2/ai-envelope.schema.json +167 -0
  111. package/schemas/v2/annotation-create.schema.json +109 -0
  112. package/schemas/v2/annotation.schema.json +142 -0
  113. package/schemas/v2/artifact-type-pack-manifest.schema.json +245 -0
  114. package/schemas/v2/audit-verify-result.schema.json +110 -0
  115. package/schemas/v2/budget-policy.schema.json +61 -0
  116. package/schemas/v2/capabilities.schema.json +7644 -0
  117. package/schemas/v2/certification-bundle.schema.json +401 -0
  118. package/schemas/v2/channel-presence-payload.schema.json +60 -0
  119. package/schemas/v2/channel-written-payload.schema.json +64 -0
  120. package/schemas/v2/chat-card-pack-manifest.schema.json +254 -0
  121. package/schemas/v2/compact-tool-descriptor.schema.json +108 -0
  122. package/schemas/v2/compensation-policy.schema.json +91 -0
  123. package/schemas/v2/compensation-projection.schema.json +95 -0
  124. package/schemas/v2/configurable.schema.json +110 -0
  125. package/schemas/v2/connection-pack-manifest.schema.json +354 -0
  126. package/schemas/v2/conversation-event.schema.json +281 -0
  127. package/schemas/v2/conversation-turn.schema.json +165 -0
  128. package/schemas/v2/credential-provenance.schema.json +62 -0
  129. package/schemas/v2/credential-reference.schema.json +28 -0
  130. package/schemas/v2/debug-bundle.schema.json +312 -0
  131. package/schemas/v2/dispatch-config.schema.json +147 -0
  132. package/schemas/v2/effect-ledger-projection.schema.json +71 -0
  133. package/schemas/v2/effect-seam-manifest.schema.json +98 -0
  134. package/schemas/v2/envelopes/clarification.request.schema.json +59 -0
  135. package/schemas/v2/envelopes/error.schema.json +35 -0
  136. package/schemas/v2/envelopes/media.audio.schema.json +41 -0
  137. package/schemas/v2/envelopes/media.file.schema.json +40 -0
  138. package/schemas/v2/envelopes/media.image.schema.json +36 -0
  139. package/schemas/v2/envelopes/schema.request.schema.json +29 -0
  140. package/schemas/v2/envelopes/schema.response.schema.json +26 -0
  141. package/schemas/v2/envelopes/ui.a2ui-surface.schema.json +292 -0
  142. package/schemas/v2/error-envelope.schema.json +229 -0
  143. package/schemas/v2/eval-summary.schema.json +166 -0
  144. package/schemas/v2/export-bundle.schema.json +94 -0
  145. package/schemas/v2/form-content-pack-manifest.schema.json +241 -0
  146. package/schemas/v2/frontend-plugin-manifest.schema.json +152 -0
  147. package/schemas/v2/goal.schema.json +188 -0
  148. package/schemas/v2/heartbeat-evaluated.schema.json +34 -0
  149. package/schemas/v2/heartbeat-state-changed.schema.json +31 -0
  150. package/schemas/v2/ids.schema.json +31 -0
  151. package/schemas/v2/localized-content-language-settings.schema.json +34 -0
  152. package/schemas/v2/localized-content-page-response.schema.json +94 -0
  153. package/schemas/v2/localized-content-page.schema.json +87 -0
  154. package/schemas/v2/localized-content-section.schema.json +63 -0
  155. package/schemas/v2/memory-entry.schema.json +45 -0
  156. package/schemas/v2/memory-list-options.schema.json +41 -0
  157. package/schemas/v2/node-pack-manifest.schema.json +657 -0
  158. package/schemas/v2/orchestrator-decision.schema.json +120 -0
  159. package/schemas/v2/org-chart-responsibility-view.schema.json +36 -0
  160. package/schemas/v2/pack-lockfile.schema.json +139 -0
  161. package/schemas/v2/prompt-kind.schema.json +14 -0
  162. package/schemas/v2/prompt-pack-manifest.schema.json +118 -0
  163. package/schemas/v2/prompt-ref.schema.json +42 -0
  164. package/schemas/v2/prompt-template.schema.json +209 -0
  165. package/schemas/v2/proposal.schema.json +145 -0
  166. package/schemas/v2/registry-version-manifest.schema.json +292 -0
  167. package/schemas/v2/residency.schema.json +19 -0
  168. package/schemas/v2/run-ancestry-response.schema.json +68 -0
  169. package/schemas/v2/run-diff-response.schema.json +80 -0
  170. package/schemas/v2/run-event-payloads.schema.json +4588 -0
  171. package/schemas/v2/run-event.schema.json +324 -0
  172. package/schemas/v2/run-options.schema.json +90 -0
  173. package/schemas/v2/run-orchestrator-decided-event.schema.json +22 -0
  174. package/schemas/v2/run-snapshot.schema.json +207 -0
  175. package/schemas/v2/security-advisory.schema.json +138 -0
  176. package/schemas/v2/self-hosted-runner-dispatch-frame.schema.json +119 -0
  177. package/schemas/v2/self-hosted-runner-registration.schema.json +74 -0
  178. package/schemas/v2/self-hosted-runner-result-frame.schema.json +47 -0
  179. package/schemas/v2/subject-link.schema.json +69 -0
  180. package/schemas/v2/subject.schema.json +597 -0
  181. package/schemas/v2/suspend-request.schema.json +370 -0
  182. package/schemas/v2/tool-descriptor.schema.json +150 -0
  183. package/schemas/v2/trigger-event.schema.json +508 -0
  184. package/schemas/v2/trigger-subscription-registration.schema.json +99 -0
  185. package/schemas/v2/trigger-subscription.schema.json +79 -0
  186. package/schemas/v2/ui-plugin-message.schema.json +149 -0
  187. package/schemas/v2/workflow-chain-pack-manifest.schema.json +610 -0
  188. package/schemas/v2/workflow-definition.schema.json +864 -0
  189. package/schemas/v2/workload-identity.schema.json +115 -0
  190. package/schemas/v2/workspace-file-create.schema.json +23 -0
  191. package/schemas/v2/workspace-file.schema.json +45 -0
  192. package/schemas/workflow-chain-pack-manifest.schema.json +615 -0
  193. package/schemas/workflow-definition.schema.json +850 -0
  194. package/schemas/workload-identity.schema.json +79 -0
  195. package/schemas/workspace-file-create.schema.json +20 -0
  196. package/schemas/workspace-file.schema.json +39 -0
  197. package/spec/v1/alias-detectors.json +283 -0
  198. package/spec/v1/capability-declaration-classes.json +176 -0
  199. package/spec/v1/core-standard-manifest.json +423 -0
  200. package/spec/v1/deprecations.json +854 -0
  201. package/spec/v1/deprecations.schema.json +187 -0
  202. package/spec/v1/event-codemap.json +1089 -0
  203. package/spec/v1/event-codemap.schema.json +41 -0
  204. package/spec/v1/extensions.json +1273 -0
  205. package/spec/v1/gaps.json +14581 -0
  206. package/spec/v1/gaps.schema.json +55 -0
  207. package/spec/v1/migrations.json +1822 -0
  208. package/spec/v1/migrations.schema.json +44 -0
  209. package/spec/v1/operation-path-manifest.json +462 -0
  210. package/spec/v1/spec-gaps.json +4823 -0
  211. package/spec/v2/declaration.json +1862 -0
  212. package/spec/v2/declaration.schema.json +305 -0
  213. package/spec/v2/errors.json +837 -0
  214. package/spec/v2/event-codemap.json +1099 -0
  215. package/spec/v2/event-codemap.schema.json +114 -0
  216. package/spec/v2/ext/provider-idempotency/registry.json +6 -0
  217. package/spec/v2/facets/a2a.schema.json +51 -0
  218. package/spec/v2/facets/aiProviders.schema.json +56 -0
  219. package/spec/v2/facets/auth.schema.json +91 -0
  220. package/spec/v2/facets/heartbeat.schema.json +20 -0
  221. package/spec/v2/facets/interrupt.schema.json +29 -0
  222. package/spec/v2/facets/mcp.schema.json +81 -0
  223. package/spec/v2/facets/replay.schema.json +34 -0
  224. package/spec/v2/facets/sandbox.schema.json +31 -0
  225. package/spec/v2/facets/webhooks.schema.json +18 -0
  226. package/spec/v2/path-manifest.json +279 -0
  227. package/spec/v2/peer-dependency-aliases.json +165 -0
  228. package/spec/v2/profiles.json +45 -0
  229. package/spec/v2/release.json +6 -0
@@ -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
+ }
@@ -0,0 +1,109 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/frontend-plugin-manifest.schema.json",
4
+ "title": "FrontendPluginManifest",
5
+ "description": "RFC 0117. Manifest for a published OpenWOP front-end plugin pack — `pack.json` at the pack root with `kind: \"frontend-plugin\"`. Peer to and disjoint from `node-pack-manifest.schema.json` (RFC 0003), `connection-pack-manifest.schema.json` (RFC 0095), and the other pack kinds via the `kind` discriminator. A front-end plugin pack distributes a SIGNED, SANDBOXED user-interface extension (a canvas editor, a custom artifact viewer, a settings panel) that a host loads at runtime in an ORIGIN/EXECUTION-ISOLATED sandbox (a cross-origin iframe by default, or an equivalent WASM/process/container/VM sandbox per `capabilities.uiPlugins.isolation`, RFC 0119) and enables through its own admin surface. The wire owns only the manifest shape, the isolation model, and the `ui-plugin/1` host-RPC boundary — NOT a rendering runtime: the plugin `entry` bundle is opaque bytes the host runs in a sandbox. Carries NO secret and NO backend `runtime` (a `runtime` member is rejected — a plugin is sandboxed UI, not a node entry). Requires the host to advertise `capabilities.uiPlugins.supported: true`; degrades to nothing on hosts without it. See `spec/v1/frontend-plugin-packs.md`.",
6
+ "type": "object",
7
+ "required": ["name", "version", "kind", "engines", "uiPlugins"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "name": {
11
+ "type": "string",
12
+ "description": "Reverse-DNS pack name per `node-packs.md` §Naming. Reserved scopes are identical (`core.*` / `vendor.<org>.*` / `community.<author>.*` / `private.<host>.*`). Mirror of `connection-pack-manifest.schema.json#/properties/name`.",
13
+ "pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
14
+ "minLength": 1,
15
+ "maxLength": 256
16
+ },
17
+ "version": {
18
+ "type": "string",
19
+ "description": "Pack-level SemVer 2.0.0.",
20
+ "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$"
21
+ },
22
+ "kind": {
23
+ "type": "string",
24
+ "const": "frontend-plugin",
25
+ "description": "Pack kind discriminator. MUST be the literal string `\"frontend-plugin\"` for this schema."
26
+ },
27
+ "description": { "type": "string", "maxLength": 1024 },
28
+ "author": { "type": "string" },
29
+ "license": { "type": "string", "description": "SPDX license identifier (e.g., `Apache-2.0`)." },
30
+ "homepage": { "type": "string", "format": "uri" },
31
+ "engines": {
32
+ "type": "object",
33
+ "description": "Compatibility floor. `openwop` is the spec range the pack targets; mirror of the other pack manifests.",
34
+ "required": ["openwop"],
35
+ "additionalProperties": false,
36
+ "properties": {
37
+ "openwop": {
38
+ "type": "string",
39
+ "description": "SemVer range of the OpenWOP spec this pack targets (e.g., `>=1.2.0`)."
40
+ }
41
+ }
42
+ },
43
+ "runtime": {
44
+ "not": {},
45
+ "description": "FORBIDDEN. A front-end plugin is sandboxed UI, not a backend node `runtime`. A manifest that carries `runtime` under `kind: \"frontend-plugin\"` MUST be rejected at registration with `pack_kind_invalid` (RFC 0117 §Negative). Declared here as `not: {}` so any value fails validation."
46
+ },
47
+ "uiPlugins": {
48
+ "type": "array",
49
+ "description": "The plugin surfaces this pack supplies. A pack MUST declare at least one. A host installs the pack but renders only those whose `surface` is in `capabilities.uiPlugins.surfaces` and whose `hostApi` methods are a subset of `capabilities.uiPlugins.hostApi` — the rest are installable-but-inert (§Degradation), never an error.",
50
+ "minItems": 1,
51
+ "items": { "$ref": "#/$defs/uiPlugin" }
52
+ }
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
+ },
59
+ "$defs": {
60
+ "uiPlugin": {
61
+ "type": "object",
62
+ "required": ["pluginId", "surface", "entry", "hostApi"],
63
+ "additionalProperties": false,
64
+ "properties": {
65
+ "pluginId": {
66
+ "type": "string",
67
+ "description": "Stable identifier of this plugin within the pack. A host enables/disables plugins by `pluginId` in its admin surface.",
68
+ "pattern": "^[a-z][a-z0-9-]{0,63}$"
69
+ },
70
+ "surface": {
71
+ "type": "string",
72
+ "description": "Which host surface this plugin renders into. `artifact-viewer` renders a typed artifact (RFC 0071) in a sandbox; `route` mounts a standalone admin/tool page; `settings-panel` contributes a configuration panel; `canvas-preview` renders the live document preview inside a host-owned canvas editor (RFC 0130). A host degrades a `surface` it does not advertise to nothing (§Degradation).",
73
+ "enum": ["artifact-viewer", "route", "settings-panel", "canvas-preview"]
74
+ },
75
+ "entry": {
76
+ "type": "string",
77
+ "description": "Pack-relative path to the plugin's entry bundle (the opaque bytes loaded in the cross-origin sandbox). Flat namespace; MUST NOT contain `..` or a leading `/` (the pattern forbids both). Required — a `uiPlugins[]` entry missing `entry` fails validation (RFC 0117 §Negative).",
78
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$"
79
+ },
80
+ "hostApi": {
81
+ "type": "array",
82
+ "description": "The closed allowlist of `ui-plugin/1` host-RPC methods this plugin is permitted to call. The host MUST reject (with an `error` response, never silent execution) any method not in BOTH this declared list AND the host's `capabilities.uiPlugins.hostApi` set (`frontend-plugin-rpc-allowlist`). The plugin holds no credentials; every method is host-mediated and authz-checked.",
83
+ "items": {
84
+ "type": "string",
85
+ "enum": ["artifact.read", "artifact.write", "host.toast", "host.navigate", "host.announce"]
86
+ },
87
+ "uniqueItems": true
88
+ },
89
+ "canvasTypes": {
90
+ "type": "array",
91
+ "description": "OPTIONAL; meaningful only for `surface: \"canvas-preview\"` (RFC 0130): the host canvas type id(s) this preview renders. A host mounts the plugin only for matching canvas documents and MUST ignore entries it does not recognize; absent/empty means never auto-mounted.",
92
+ "items": { "type": "string", "maxLength": 128 },
93
+ "uniqueItems": true
94
+ },
95
+ "connectSrc": {
96
+ "type": "array",
97
+ "description": "OPTIONAL explicit `connect-src` CSP exceptions the plugin needs (e.g., a font CDN). Absent → the host serves the plugin under a DENY-EGRESS CSP (`frontend-plugin-egress`): no network egress beyond the host-RPC channel. Any entry here is part of the front-end-review checkpoint (`registry-operations.md`) a reviewer audits; a host MAY refuse a pack whose exceptions it will not grant.",
98
+ "items": { "type": "string", "format": "uri" },
99
+ "uniqueItems": true
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
+ }
106
+ }
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/goal.schema.json",
4
+ "title": "Goal",
5
+ "description": "RFC 0097 §B. A standing goal — a durable objective with explicit completion criteria, evaluated by a host-side judge (RFC 0090 verifier verdict or an opaque host evaluator), that keeps an agent working across turns and runs until the judge is satisfied, a declared RFC 0058 bound is crossed, or the agent escalates (RFC 0044). Completion MUST be the judge's verdict — a client MUST NOT set `state: satisfied` directly. Continuation MUST be bounded. `objective` and any verdict payload are SR-1 redaction-safe. Gated on the `agents.goals` capability.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["id", "objective", "state", "completion", "continuation", "bounds", "owner", "createdAt"],
9
+ "properties": {
10
+ "id": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "description": "Host-assigned identifier."
14
+ },
15
+ "objective": {
16
+ "type": "string",
17
+ "description": "The standing objective. SR-1 redaction-safe — no secrets/PII."
18
+ },
19
+ "state": {
20
+ "type": "string",
21
+ "enum": ["active", "satisfied", "escalated", "abandoned", "bound-exceeded"],
22
+ "description": "Lifecycle state. `satisfied` is set ONLY by the host on a judge verdict, never by a client. `bound-exceeded` is set when a declared bound is crossed; `escalated` when an RFC 0044 confidence-escalation interrupt fires against the goal; `abandoned` on explicit abandon."
23
+ },
24
+ "completion": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "required": ["check"],
28
+ "description": "How the goal's completion is judged.",
29
+ "properties": {
30
+ "check": {
31
+ "type": "string",
32
+ "enum": ["verifier", "host"],
33
+ "description": "`verifier`: completion is an RFC 0090 verifier verdict. `host`: an opaque host evaluator."
34
+ },
35
+ "verifierRef": {
36
+ "type": ["string", "null"],
37
+ "default": null,
38
+ "description": "RFC 0090 verifier id when `check = verifier`."
39
+ },
40
+ "lastVerdict": {
41
+ "type": ["object", "null"],
42
+ "default": null,
43
+ "additionalProperties": false,
44
+ "description": "Most recent judge verdict. Content-free re: objective text. Non-deterministic judge output — MUST be persisted (carried in the `goal.evaluated` event / checkpoint) and never recomputed on replay/fork.",
45
+ "properties": {
46
+ "satisfied": { "type": "boolean" },
47
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
48
+ "runId": { "type": "string", "description": "The run this verdict evaluated (RFC 0040 causation-compatible)." }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "continuation": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": ["mode"],
57
+ "description": "How the goal re-engages work between judge checks.",
58
+ "properties": {
59
+ "mode": {
60
+ "type": "string",
61
+ "enum": ["schedule", "commitment", "heartbeat", "manual"],
62
+ "description": "`schedule` (RFC 0052), `commitment` (RFC 0068), `heartbeat` (RFC 0060), `manual`."
63
+ },
64
+ "armRef": {
65
+ "type": ["string", "null"],
66
+ "default": null,
67
+ "description": "The RFC 0052 job / RFC 0068 commitment / RFC 0060 heartbeat that re-engages work for this goal."
68
+ }
69
+ }
70
+ },
71
+ "bounds": {
72
+ "type": "object",
73
+ "additionalProperties": false,
74
+ "description": "RFC 0058 execution bounds. The host MUST stop continuation and set `state: bound-exceeded` when any declared bound is crossed. When `agents.goals.requiresBounds` is true, at least one bound MUST be present before the goal may activate.",
75
+ "properties": {
76
+ "maxLoopIterations": { "type": "integer", "minimum": 1, "description": "RFC 0058. Max contributing iterations before the goal is bound-exceeded." },
77
+ "runTimeoutMs": { "type": "integer", "minimum": 0, "description": "RFC 0058. Wall-clock deadline (ms) for the standing goal." },
78
+ "maxCostUsd": { "type": "number", "minimum": 0, "description": "RFC 0084. Accumulated cost ceiling across contributing runs." }
79
+ }
80
+ },
81
+ "progress": {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "description": "Continuation progress so far.",
85
+ "properties": {
86
+ "iterations": { "type": "integer", "minimum": 0 },
87
+ "contributingRunIds": { "type": "array", "items": { "type": "string" }, "description": "RFC 0040 causation-compatible." }
88
+ }
89
+ },
90
+ "owner": {
91
+ "type": "object",
92
+ "additionalProperties": false,
93
+ "required": ["tenant"],
94
+ "description": "RFC 0048 identity triple. Redaction-safe — `principal` is an opaque id, never PII or credential material.",
95
+ "properties": {
96
+ "tenant": { "type": "string", "minLength": 1 },
97
+ "workspace": { "type": "string", "minLength": 1 },
98
+ "principal": { "type": "string", "minLength": 1 }
99
+ }
100
+ },
101
+ "createdAt": { "type": "string", "format": "date-time" },
102
+ "updatedAt": { "type": "string", "format": "date-time" }
103
+ }
104
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/heartbeat-evaluated.schema.json",
4
+ "title": "HeartbeatEvaluated",
5
+ "description": "RFC 0060. Emitted every tick by a host advertising `capabilities.heartbeat.supported: true` — the observability record of a heartbeat predicate evaluation. Heartbeat-scoped (NOT a replayable run-event-log entry). Carries no external state or secret material; `changed` gates whether a `heartbeat.stateChanged` + enqueued run follow.",
6
+ "type": "object",
7
+ "required": ["heartbeatId", "status", "changed"],
8
+ "properties": {
9
+ "heartbeatId": { "type": "string", "minLength": 1, "description": "Host-assigned identifier for the heartbeat whose tick this records." },
10
+ "status": { "type": "string", "enum": ["ok", "timeout", "error"], "description": "`ok` — predicate evaluated within `maxRuntimeMs`. `timeout` — terminated at the budget (RFC 0060 §B.2). `error` — the predicate threw." },
11
+ "changed": { "type": "boolean", "description": "Whether the predicate's state transitioned vs. the prior tick. `true` is the only path that may emit `heartbeat.stateChanged` and enqueue a run." }
12
+ },
13
+ "additionalProperties": false
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/heartbeat-state-changed.schema.json",
4
+ "title": "HeartbeatStateChanged",
5
+ "description": "RFC 0060. Emitted ONLY on a predicate-state transition — an unchanged tick MUST NOT emit it (this is what prevents notification spam). Heartbeat-scoped (NOT a replayable run-event-log entry). `from`/`to` are opaque host-persisted predicate-state tokens; they MUST NOT carry secret or external-content material beyond the minimal state needed to detect a transition.",
6
+ "type": "object",
7
+ "required": ["heartbeatId", "from", "to"],
8
+ "properties": {
9
+ "heartbeatId": { "type": "string", "minLength": 1, "description": "Host-assigned identifier for the heartbeat that transitioned." },
10
+ "from": { "type": "object", "additionalProperties": true, "description": "Prior tick's predicate state (opaque, host-defined)." },
11
+ "to": { "type": "object", "additionalProperties": true, "description": "Current tick's predicate state — the transition target (opaque, host-defined)." }
12
+ },
13
+ "additionalProperties": false
14
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/localized-content-language-settings.schema.json",
4
+ "title": "LocalizedContentLanguageSettings",
5
+ "description": "RFC 0103 (spec/v1/localized-content.md §B). Per-tenant authoring configuration for the content surface. Invariant: `baseLocale` MUST NOT appear in `supportedLocales` (the base locale is carried by section `data`, supported locales by `localizations`). This object is the source of truth for the host's advertised `content` capability block; the advertisement MUST reflect it (`baseLocale` equal, advertised `supportedLocales` equal or a subset).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["baseLocale", "supportedLocales", "autoTranslateOnPublish"],
9
+ "properties": {
10
+ "baseLocale": {
11
+ "type": "string",
12
+ "pattern": "^[a-z]{2}(-[A-Z]{2})?$",
13
+ "description": "The locale section `data` is authored in. MUST equal `capabilities.i18n.defaultLocale`."
14
+ },
15
+ "supportedLocales": {
16
+ "type": "array",
17
+ "description": "BCP-47 tags the tenant authors content translations for. MUST NOT contain `baseLocale`.",
18
+ "items": { "type": "string", "pattern": "^[a-z]{2}(-[A-Z]{2})?$" },
19
+ "uniqueItems": true
20
+ },
21
+ "autoTranslateOnPublish": {
22
+ "type": "boolean",
23
+ "description": "Whether the host auto-translates missing locale fields on publish (host-extension behavior; informational at the protocol layer)."
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/localized-content-page-response.schema.json",
4
+ "title": "LocalizedContentPageResponse",
5
+ "description": "RFC 0103 (spec/v1/localized-content.md §D). The resolved delivery response for `GET /v1/content/pages/{slug}`. `locale` is the negotiated locale (equals the `Content-Language` response header). `sections[]` carry the ALREADY-MERGED section bodies — resolution (the per-section field merge §C) happened server-side, so no `localizations` map appears in the response. Only `published`, `enabled` sections are present, in render order.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["version", "generatedAt", "locale", "slug", "page", "sections"],
9
+ "properties": {
10
+ "version": {
11
+ "type": "string",
12
+ "description": "Response schema version marker (e.g. `\"1\"`)."
13
+ },
14
+ "generatedAt": {
15
+ "type": "string",
16
+ "format": "date-time",
17
+ "description": "When the resolved response was generated."
18
+ },
19
+ "locale": {
20
+ "type": "string",
21
+ "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8}){0,3}$",
22
+ "description": "The negotiated locale actually used (BCP-47). Equals the `Content-Language` response header."
23
+ },
24
+ "slug": {
25
+ "type": "string",
26
+ "pattern": "^[a-z][a-z0-9-]*$",
27
+ "description": "The requested page slug."
28
+ },
29
+ "page": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["pageId", "slug", "name"],
33
+ "description": "Resolved page metadata (status omitted — only published pages are delivered).",
34
+ "properties": {
35
+ "pageId": { "type": "string", "minLength": 1 },
36
+ "slug": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
37
+ "name": { "type": "string", "minLength": 1 },
38
+ "seo": { "type": "object", "additionalProperties": true }
39
+ }
40
+ },
41
+ "sections": {
42
+ "type": "array",
43
+ "description": "Resolved sections in render order; each body is the merged result, not the raw record.",
44
+ "items": {
45
+ "type": "object",
46
+ "additionalProperties": false,
47
+ "required": ["sectionId", "sectionType", "data"],
48
+ "properties": {
49
+ "sectionId": { "type": "string", "minLength": 1 },
50
+ "sectionType": { "type": "string", "minLength": 1 },
51
+ "data": {
52
+ "type": "object",
53
+ "additionalProperties": true,
54
+ "description": "The merged section body for the negotiated locale (exact → family → base shallow overlay)."
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }