@honua/react 0.1.4-beta.0 → 0.1.9-beta.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 (157) hide show
  1. package/contract/types.d.ts +1 -1
  2. package/contract/types.d.ts.map +1 -1
  3. package/contract/types.js +4 -0
  4. package/control-plane/admin-client.d.ts +87 -0
  5. package/control-plane/admin-client.d.ts.map +1 -0
  6. package/control-plane/admin-client.js +169 -0
  7. package/control-plane/client.d.ts.map +1 -1
  8. package/control-plane/client.js +21 -3
  9. package/control-plane/commands/catalog.d.ts +133 -0
  10. package/control-plane/commands/catalog.d.ts.map +1 -0
  11. package/control-plane/commands/catalog.js +353 -0
  12. package/control-plane/commands/errors.d.ts +96 -0
  13. package/control-plane/commands/errors.d.ts.map +1 -0
  14. package/control-plane/commands/errors.js +130 -0
  15. package/control-plane/commands/identity.d.ts +71 -0
  16. package/control-plane/commands/identity.d.ts.map +1 -0
  17. package/control-plane/commands/identity.js +0 -0
  18. package/control-plane/commands/index.d.ts +39 -0
  19. package/control-plane/commands/index.d.ts.map +1 -0
  20. package/control-plane/commands/index.js +34 -0
  21. package/control-plane/commands/runtime.d.ts +146 -0
  22. package/control-plane/commands/runtime.d.ts.map +1 -0
  23. package/control-plane/commands/runtime.js +318 -0
  24. package/control-plane/commands/types.d.ts +226 -0
  25. package/control-plane/commands/types.d.ts.map +1 -0
  26. package/control-plane/commands/types.js +30 -0
  27. package/control-plane/commands/validate.d.ts +27 -0
  28. package/control-plane/commands/validate.d.ts.map +1 -0
  29. package/control-plane/commands/validate.js +132 -0
  30. package/control-plane/generated/admin-api.d.ts +30263 -0
  31. package/control-plane/generated/admin-api.d.ts.map +1 -0
  32. package/control-plane/generated/admin-api.js +5 -0
  33. package/control-plane/generated/admin-operations.d.ts +4053 -0
  34. package/control-plane/generated/admin-operations.d.ts.map +1 -0
  35. package/control-plane/generated/admin-operations.js +820 -0
  36. package/control-plane/index.d.ts +11 -0
  37. package/control-plane/index.d.ts.map +1 -1
  38. package/control-plane/index.js +8 -0
  39. package/control-plane/types.d.ts +14 -0
  40. package/control-plane/types.d.ts.map +1 -1
  41. package/core/client.d.ts +6 -0
  42. package/core/client.d.ts.map +1 -1
  43. package/core/client.js +12 -10
  44. package/core/credential-redaction.d.ts +163 -0
  45. package/core/credential-redaction.d.ts.map +1 -0
  46. package/core/credential-redaction.js +389 -0
  47. package/core/error-classifications.d.ts +32 -0
  48. package/core/error-classifications.d.ts.map +1 -1
  49. package/core/error-classifications.js +32 -0
  50. package/core/error-code-registry.d.ts +33 -1
  51. package/core/error-code-registry.d.ts.map +1 -1
  52. package/core/error-code-registry.js +32 -0
  53. package/core/errors.d.ts +1 -1
  54. package/core/errors.d.ts.map +1 -1
  55. package/core/geoparquet-sql.d.ts +1 -1
  56. package/core/geoparquet-sql.js +2 -2
  57. package/core/grpc-adapter.d.ts +2 -2
  58. package/core/grpc-adapter.d.ts.map +1 -1
  59. package/core/grpc-adapter.js +30 -6
  60. package/core/job-run-errors.d.ts +10 -0
  61. package/core/job-run-errors.d.ts.map +1 -0
  62. package/core/job-run-errors.js +14 -0
  63. package/core/job-run-lifecycle.d.ts +34 -0
  64. package/core/job-run-lifecycle.d.ts.map +1 -0
  65. package/core/job-run-lifecycle.js +193 -0
  66. package/core/ogc-processes.d.ts +38 -31
  67. package/core/ogc-processes.d.ts.map +1 -1
  68. package/core/ogc-processes.js +123 -173
  69. package/core/process-runner.js +15 -100
  70. package/core/stac.d.ts +2 -1
  71. package/core/stac.d.ts.map +1 -1
  72. package/core/stac.js +29 -11
  73. package/core/surfaces.d.ts +2 -8
  74. package/core/surfaces.d.ts.map +1 -1
  75. package/core/surfaces.js +21 -112
  76. package/core/types.d.ts +22 -8
  77. package/core/types.d.ts.map +1 -1
  78. package/gen/geospatial/v1/common_pb.d.ts +514 -0
  79. package/gen/geospatial/v1/common_pb.d.ts.map +1 -0
  80. package/gen/geospatial/v1/common_pb.js +335 -0
  81. package/gen/geospatial/v1/execution_types_pb.d.ts +1186 -0
  82. package/gen/geospatial/v1/execution_types_pb.d.ts.map +1 -0
  83. package/gen/geospatial/v1/execution_types_pb.js +396 -0
  84. package/gen/geospatial/v1/feature_service_pb.d.ts +403 -0
  85. package/gen/geospatial/v1/feature_service_pb.d.ts.map +1 -0
  86. package/gen/geospatial/v1/feature_service_pb.js +47 -0
  87. package/gen/geospatial/v1/index.d.ts +5 -0
  88. package/gen/geospatial/v1/index.d.ts.map +1 -0
  89. package/gen/geospatial/v1/index.js +9 -0
  90. package/gen/geospatial/v1/spatial_types_pb.d.ts +285 -0
  91. package/gen/geospatial/v1/spatial_types_pb.d.ts.map +1 -0
  92. package/gen/geospatial/v1/spatial_types_pb.js +58 -0
  93. package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts +379 -0
  94. package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts.map +1 -0
  95. package/gen/geospatial/v1/workspace_artifact_types_pb.js +151 -0
  96. package/generated-app/projection.d.ts.map +1 -1
  97. package/generated-app/projection.js +10 -1
  98. package/interactions/declarative-contract.d.ts +149 -0
  99. package/interactions/declarative-contract.d.ts.map +1 -0
  100. package/interactions/declarative-contract.js +293 -0
  101. package/interactions/declarative.d.ts +198 -0
  102. package/interactions/declarative.d.ts.map +1 -0
  103. package/interactions/declarative.js +470 -0
  104. package/interactions/feature-state.d.ts +7 -0
  105. package/interactions/feature-state.d.ts.map +1 -1
  106. package/interactions/feature-state.js +10 -2
  107. package/map/maplibre-target.js +1 -1
  108. package/package.json +2 -2
  109. package/query-planner/grpc.d.ts +4 -4
  110. package/query-planner/grpc.d.ts.map +1 -1
  111. package/query-planner/grpc.js +11 -11
  112. package/query-planner/types.d.ts +4 -4
  113. package/query-planner/types.d.ts.map +1 -1
  114. package/realtime/sse.d.ts.map +1 -1
  115. package/realtime/sse.js +7 -1
  116. package/runtime/errors.d.ts +1 -1
  117. package/runtime/errors.d.ts.map +1 -1
  118. package/runtime/errors.js +2 -0
  119. package/runtime/generated/map-package-schema-meta.d.ts +11 -0
  120. package/runtime/generated/map-package-schema-meta.d.ts.map +1 -0
  121. package/runtime/generated/map-package-schema-meta.js +12 -0
  122. package/runtime/generated/map-package-schema-validator.d.ts +16 -0
  123. package/runtime/generated/map-package-schema-validator.d.ts.map +1 -0
  124. package/runtime/generated/map-package-schema-validator.js +1422 -0
  125. package/runtime/index.d.ts +5 -1
  126. package/runtime/index.d.ts.map +1 -1
  127. package/runtime/index.js +6 -0
  128. package/runtime/map-package-export.d.ts +151 -0
  129. package/runtime/map-package-export.d.ts.map +1 -0
  130. package/runtime/map-package-export.js +636 -0
  131. package/runtime/map-package-validation.d.ts +21 -1
  132. package/runtime/map-package-validation.d.ts.map +1 -1
  133. package/runtime/map-package-validation.js +132 -95
  134. package/runtime/map-package.d.ts +80 -0
  135. package/runtime/map-package.d.ts.map +1 -1
  136. package/runtime/style-compose.d.ts.map +1 -1
  137. package/runtime/style-compose.js +78 -9
  138. package/studio/chart-spec.d.ts +6 -6
  139. package/studio/chart-spec.d.ts.map +1 -1
  140. package/studio/chart-spec.js +6 -6
  141. package/studio/index.d.ts +17 -7
  142. package/studio/index.d.ts.map +1 -1
  143. package/studio/index.js +15 -5
  144. package/studio/lifecycle-client.d.ts +262 -22
  145. package/studio/lifecycle-client.d.ts.map +1 -1
  146. package/studio/lifecycle-client.js +524 -24
  147. package/studio/lifecycle-errors.d.ts +8 -2
  148. package/studio/lifecycle-errors.d.ts.map +1 -1
  149. package/studio/lifecycle-errors.js +6 -1
  150. package/studio/lifecycle-types.d.ts +228 -8
  151. package/studio/lifecycle-types.d.ts.map +1 -1
  152. package/studio/publication-status.d.ts +55 -0
  153. package/studio/publication-status.d.ts.map +1 -0
  154. package/studio/publication-status.js +103 -0
  155. package/gen/honua/v1/feature_service_pb.d.ts +0 -918
  156. package/gen/honua/v1/feature_service_pb.d.ts.map +0 -1
  157. package/gen/honua/v1/feature_service_pb.js +0 -375
@@ -0,0 +1,353 @@
1
+ /**
2
+ * The 2026.1 command catalog: real commands over the existing control-plane and
3
+ * Studio resource clients.
4
+ *
5
+ * These four cover one end-to-end slice of the terminal release journey —
6
+ * probe a connection, start an import, publish a map package, and save a Studio
7
+ * draft as an immutable version — and exercise every capability the shared
8
+ * layer promises: idempotency, dry run, cancellation, validation, typed errors,
9
+ * and deterministic receipts. Each command owns its own sequencing, which is
10
+ * why `studio.draft.saveVersion` performs the optimistic-generation check here
11
+ * rather than in four transports.
12
+ *
13
+ * ## Where the concurrency check is bracketed, not atomic
14
+ *
15
+ * `studio.draft.saveVersion` cannot make its generation check atomic with the
16
+ * write, because the Studio lifecycle API offers nothing to make it atomic
17
+ * with: `POST /package-drafts/{draftId}/content-versions` takes no request body
18
+ * (`HonuaStudioDraftsClient.createContentVersion`, and the pinned contract
19
+ * fixture `test/fixtures/studio-lifecycle/content-version-create.v1.json`) and
20
+ * `HonuaStudioRequestOptions` has no `ifMatch` — unlike its control-plane
21
+ * sibling `HonuaControlPlaneRequestOptions`. The one optimistic-concurrency
22
+ * primitive in the surface is the `generation` field on the
23
+ * `PUT /package-drafts/{draftId}` *body* (`StudioPackageDraftReplaceRequest`),
24
+ * which this route has no analogue for.
25
+ *
26
+ * So the command brackets the write instead of pretending the check was
27
+ * atomic: it reads the generation before, re-reads it after, and raises a
28
+ * `conflict` naming the version it created if the draft moved at any point.
29
+ * `generation` is documented to increment on every successful `PUT`
30
+ * (`StudioPackageDraft`), so no concurrent edit can slip through the bracket
31
+ * unseen. When honua-server grows a real precondition on this route, the
32
+ * bracket collapses into it and the second read goes away.
33
+ *
34
+ * @experimental
35
+ * @module
36
+ */
37
+ import { HonuaCommandError } from "./errors.js";
38
+ /**
39
+ * `POST /connections/{connectionId}/test` — probe a stored connection.
40
+ *
41
+ * Modelled as an `action` rather than a `read` so repeated probes carry an
42
+ * `Idempotency-Key` and collapse server-side; the probe itself does not mutate
43
+ * the connection.
44
+ */
45
+ export const connectionTestCommand = {
46
+ id: "connection.test",
47
+ title: "Test a connection",
48
+ description: "Probe a stored control-plane connection and report whether the server can reach it.",
49
+ mode: "action",
50
+ resourceKind: "connection",
51
+ inputSchema: {
52
+ type: "object",
53
+ description: "Identifies the stored connection to probe.",
54
+ properties: {
55
+ connectionId: { type: "string", minLength: 1, description: "Identifier of the stored connection." },
56
+ workspaceId: { type: "string", minLength: 1, description: "Owning workspace, when the deployment scopes them." },
57
+ },
58
+ required: ["connectionId"],
59
+ additionalProperties: false,
60
+ },
61
+ plan(context) {
62
+ return {
63
+ method: "POST",
64
+ path: connectionTestPath(context.input.connectionId),
65
+ summary: `Probe connection ${context.input.connectionId}`,
66
+ resourceRef: connectionRef(context.input),
67
+ };
68
+ },
69
+ async execute(context) {
70
+ const result = await context.controlPlane.raw({
71
+ method: "POST",
72
+ path: connectionTestPath(context.input.connectionId),
73
+ ...(context.input.workspaceId ? { body: { workspaceId: context.input.workspaceId } } : {}),
74
+ ...context.requestOptions(),
75
+ });
76
+ const value = unwrap(result, this.id, {
77
+ correlationId: context.correlationId,
78
+ idempotencyKey: context.idempotencyKey,
79
+ });
80
+ return {
81
+ output: value ?? {},
82
+ ...(value?.ok === false ? { status: "denied" } : {}),
83
+ resourceRef: connectionRef(context.input),
84
+ };
85
+ },
86
+ };
87
+ function connectionTestPath(connectionId) {
88
+ return `/connections/${encodeURIComponent(connectionId)}/test`;
89
+ }
90
+ function connectionRef(input) {
91
+ return {
92
+ type: "connection",
93
+ id: input.connectionId,
94
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
95
+ };
96
+ }
97
+ /** `POST /imports` — enqueue an import job. */
98
+ export const importCreateCommand = {
99
+ id: "import.create",
100
+ title: "Create an import job",
101
+ description: "Enqueue a control-plane import job for a source URL or a stored connection.",
102
+ mode: "action",
103
+ resourceKind: "import-job",
104
+ inputSchema: {
105
+ type: "object",
106
+ description: "Describes the source to import.",
107
+ properties: {
108
+ sourceKind: { type: "string", minLength: 1, description: "Import source kind, e.g. `geojson` or `postgis`." },
109
+ sourceUrl: { type: "string", minLength: 1, description: "Source URL, when the import reads a location." },
110
+ connectionId: { type: "string", minLength: 1, description: "Stored connection, when the import reads a system." },
111
+ workspaceId: { type: "string", minLength: 1, description: "Workspace that will own the imported content." },
112
+ title: { type: "string", minLength: 1, description: "Human title for the resulting content." },
113
+ options: { type: "object", description: "Import-kind-specific options, passed through verbatim." },
114
+ },
115
+ required: ["sourceKind"],
116
+ additionalProperties: false,
117
+ },
118
+ validate(input) {
119
+ // JSON Schema cannot say "one of these two", and this has to run before the
120
+ // dry-run short circuit: a preview that accepts an import with no source
121
+ // would be a preview of a request the server never sees.
122
+ if (!input.sourceUrl && !input.connectionId) {
123
+ return [{ path: "", message: "one of `sourceUrl` or `connectionId` is required" }];
124
+ }
125
+ return [];
126
+ },
127
+ plan(context) {
128
+ const { input } = context;
129
+ return {
130
+ method: "POST",
131
+ path: "/imports",
132
+ summary: `Import ${input.sourceKind} from ${input.sourceUrl ?? input.connectionId ?? "(unspecified source)"}`,
133
+ resourceRef: {
134
+ type: "import-job",
135
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
136
+ },
137
+ };
138
+ },
139
+ async execute(context) {
140
+ const { input } = context;
141
+ const result = await context.controlPlane.imports.create({
142
+ sourceKind: input.sourceKind,
143
+ ...(input.sourceUrl ? { sourceUrl: input.sourceUrl } : {}),
144
+ ...(input.connectionId ? { connectionId: input.connectionId } : {}),
145
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
146
+ ...(input.title ? { title: input.title } : {}),
147
+ ...(input.options ? { options: input.options } : {}),
148
+ }, context.requestOptions());
149
+ const job = unwrap(result, this.id, {
150
+ correlationId: context.correlationId,
151
+ idempotencyKey: context.idempotencyKey,
152
+ });
153
+ return {
154
+ output: job,
155
+ resourceRef: {
156
+ type: "import-job",
157
+ ...(job?.id ? { id: job.id } : {}),
158
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
159
+ ...(job?.links?.self ? { href: job.links.self } : {}),
160
+ },
161
+ ...(validatorsOf(job) ? { validators: validatorsOf(job) } : {}),
162
+ ...(job?.problem ? { problem: job.problem } : {}),
163
+ };
164
+ },
165
+ };
166
+ /** `POST /packages` — publish a map package version. */
167
+ export const mapPackagePublishCommand = {
168
+ id: "map-package.publish",
169
+ title: "Publish a map package",
170
+ description: "Publish a map package version. Approval and visibility remain server-side decisions.",
171
+ mode: "action",
172
+ resourceKind: "map-package",
173
+ inputSchema: {
174
+ type: "object",
175
+ description: "The map package to publish and where it belongs.",
176
+ properties: {
177
+ mapId: { type: "string", minLength: 1, description: "Hosted map the package version belongs to." },
178
+ workspaceId: { type: "string", minLength: 1, description: "Owning workspace." },
179
+ package: { type: "object", description: "The `honua_map_package.v1` document." },
180
+ message: { type: "string", description: "Publication message recorded with the version." },
181
+ },
182
+ required: ["package"],
183
+ additionalProperties: false,
184
+ },
185
+ plan(context) {
186
+ const { input } = context;
187
+ return {
188
+ method: "POST",
189
+ path: "/packages",
190
+ summary: input.mapId ? `Publish a package version for map ${input.mapId}` : "Publish a map package version",
191
+ resourceRef: {
192
+ type: "map-package",
193
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
194
+ },
195
+ };
196
+ },
197
+ async execute(context) {
198
+ const { input } = context;
199
+ const result = await context.controlPlane.packages.publish({
200
+ package: input.package,
201
+ ...(input.mapId ? { mapId: input.mapId } : {}),
202
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
203
+ ...(input.message ? { message: input.message } : {}),
204
+ }, context.requestOptions());
205
+ const response = unwrap(result, this.id, {
206
+ correlationId: context.correlationId,
207
+ idempotencyKey: context.idempotencyKey,
208
+ });
209
+ return {
210
+ output: response,
211
+ resourceRef: {
212
+ type: "map-package",
213
+ ...(response?.packageId ? { id: response.packageId } : {}),
214
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
215
+ ...(response?.links?.self ? { href: response.links.self } : {}),
216
+ },
217
+ ...(validatorsOf(response) ? { validators: validatorsOf(response) } : {}),
218
+ };
219
+ },
220
+ };
221
+ /** `POST /package-drafts/{draftId}/content-versions` — save a draft as an immutable version. */
222
+ export const studioDraftSaveVersionCommand = {
223
+ id: "studio.draft.saveVersion",
224
+ title: "Save a Studio draft as a version",
225
+ description: "Freeze a mutable Studio package draft into an immutable content version, checking the generation.",
226
+ mode: "action",
227
+ resourceKind: "studio-content-version",
228
+ inputSchema: {
229
+ type: "object",
230
+ description: "Identifies the draft to freeze.",
231
+ properties: {
232
+ draftId: { type: "string", minLength: 1, description: "Mutable draft identifier." },
233
+ generation: { type: "integer", minimum: 0, description: "Last-seen draft generation for concurrency checking." },
234
+ },
235
+ required: ["draftId"],
236
+ additionalProperties: false,
237
+ },
238
+ plan(context) {
239
+ return {
240
+ method: "POST",
241
+ path: `/package-drafts/${encodeURIComponent(context.input.draftId)}/content-versions`,
242
+ summary: `Save draft ${context.input.draftId} as an immutable content version`,
243
+ resourceRef: { type: "studio-package-draft", id: context.input.draftId },
244
+ };
245
+ },
246
+ async execute(context) {
247
+ const studio = context.studio;
248
+ if (!studio) {
249
+ throw new HonuaCommandError("transport", this.id, "studio.draft.saveVersion requires a Studio lifecycle client; construct the runtime with `studio`.", { correlationId: context.correlationId, idempotencyKey: context.idempotencyKey });
250
+ }
251
+ // The Studio lifecycle client takes `{ signal, headers }` rather than the
252
+ // control-plane option bag, so the idempotency key is threaded as the
253
+ // header it becomes — and only onto the mutating call.
254
+ const options = context.requestOptions();
255
+ const readOptions = {
256
+ ...(options.signal ? { signal: options.signal } : {}),
257
+ ...(options.headers ? { headers: options.headers } : {}),
258
+ };
259
+ // The command-owned key goes on last: the runtime already refuses a caller
260
+ // header that would replace it, and this keeps the value on the wire equal
261
+ // to the value the receipt records even if that guard is ever relaxed.
262
+ const writeOptions = {
263
+ ...readOptions,
264
+ headers: { ...headersToRecord(options.headers), "Idempotency-Key": context.idempotencyKey },
265
+ };
266
+ if (context.input.generation !== undefined) {
267
+ const draft = await studio.drafts.get(context.input.draftId, readOptions);
268
+ if (draft.generation !== context.input.generation) {
269
+ throw new HonuaCommandError("conflict", this.id, `Studio draft ${context.input.draftId} moved from generation ${context.input.generation} to ${draft.generation}; reload and retry.`, { correlationId: context.correlationId, idempotencyKey: context.idempotencyKey, statusCode: 409 });
270
+ }
271
+ }
272
+ const version = await studio.drafts.createContentVersion(context.input.draftId, writeOptions);
273
+ // The lifecycle API exposes no precondition on the content-version POST, so
274
+ // the pre-read alone leaves a window in which another editor's `PUT` lands
275
+ // between the check and the write. When the caller asked for concurrency
276
+ // protection, close the bracket: re-read and refuse to report a success we
277
+ // cannot vouch for. The created version is named so the caller can
278
+ // reconcile it rather than lose track of it.
279
+ if (context.input.generation !== undefined) {
280
+ const after = await studio.drafts.get(context.input.draftId, readOptions);
281
+ if (after.generation !== context.input.generation) {
282
+ throw new HonuaCommandError("conflict", this.id, `Studio draft ${context.input.draftId} moved from generation ${context.input.generation} to ${after.generation} while version ${version?.versionId ?? "(unknown)"} was being cut; reconcile that version, then reload and retry.`, { correlationId: context.correlationId, idempotencyKey: context.idempotencyKey, statusCode: 409 });
283
+ }
284
+ }
285
+ return {
286
+ output: version,
287
+ resourceRef: {
288
+ type: "studio-content-version",
289
+ ...(version?.versionId ? { id: version.versionId } : {}),
290
+ },
291
+ };
292
+ },
293
+ };
294
+ // ---------------------------------------------------------------------------
295
+ // Registry
296
+ // ---------------------------------------------------------------------------
297
+ /** Every command id in the 2026.1 catalog, in registry order. */
298
+ export const HONUA_COMMAND_IDS = [
299
+ "connection.test",
300
+ "import.create",
301
+ "map-package.publish",
302
+ "studio.draft.saveVersion",
303
+ ];
304
+ /**
305
+ * The shared registry every transport dispatches through. MCP tool schemas,
306
+ * CLI help, Studio clients, and JS methods all project from these entries.
307
+ */
308
+ export const HONUA_COMMANDS = {
309
+ "connection.test": connectionTestCommand,
310
+ "import.create": importCreateCommand,
311
+ "map-package.publish": mapPackagePublishCommand,
312
+ "studio.draft.saveVersion": studioDraftSaveVersionCommand,
313
+ };
314
+ /** Type guard narrowing an arbitrary string to a registered command id. */
315
+ export function isHonuaCommandId(value) {
316
+ return Object.hasOwn(HONUA_COMMANDS, value);
317
+ }
318
+ // ---------------------------------------------------------------------------
319
+ // Shared helpers
320
+ // ---------------------------------------------------------------------------
321
+ /**
322
+ * Unwrap a {@link HonuaControlPlaneResult}, converting the "this deployment
323
+ * does not expose the capability" arm into a typed `transport` failure so every
324
+ * transport reports the gap the same way.
325
+ */
326
+ function unwrap(result, commandId, trace) {
327
+ if (result.supported)
328
+ return result.value;
329
+ throw new HonuaCommandError("transport", commandId, result.reason, {
330
+ correlationId: trace.correlationId,
331
+ idempotencyKey: trace.idempotencyKey,
332
+ ...(result.statusCode !== undefined ? { statusCode: result.statusCode } : {}),
333
+ ...(result.problem ? { problem: result.problem } : {}),
334
+ });
335
+ }
336
+ function headersToRecord(headers) {
337
+ if (!headers)
338
+ return {};
339
+ if (headers instanceof Headers)
340
+ return Object.fromEntries(headers.entries());
341
+ if (Array.isArray(headers))
342
+ return Object.fromEntries(headers);
343
+ return { ...headers };
344
+ }
345
+ function validatorsOf(value) {
346
+ if (!value)
347
+ return undefined;
348
+ const etag = value.etag;
349
+ const lastModified = value.lastModified;
350
+ if (!etag && !lastModified)
351
+ return undefined;
352
+ return { ...(etag ? { etag } : {}), ...(lastModified ? { lastModified } : {}) };
353
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Typed failure taxonomy for the shared control-plane command layer.
3
+ *
4
+ * Every transport surfaces the *same* five discriminants for the same
5
+ * underlying failure, so a negative RBAC or cross-tenant test written once
6
+ * behaves identically from the CLI, MCP, Studio, and direct JS.
7
+ *
8
+ * Like {@link ../../studio/lifecycle-errors.js | HonuaStudioError} and
9
+ * `HonuaCollaborationError`, this module intentionally does not join the
10
+ * central `HonuaSdkError` registry (`src/core/error-code-registry.ts`):
11
+ * `control-plane` is an `@experimental`, not-yet-semver-covered domain and a
12
+ * registry migration is scoped separately.
13
+ *
14
+ * @experimental
15
+ * @module
16
+ */
17
+ import type { HonuaProblemDetails } from "../types.js";
18
+ /**
19
+ * Failure classes a command can raise.
20
+ *
21
+ * - `validation` — input failed the command's declared JSON schema, or the
22
+ * server rejected the request shape (`400`/`422`).
23
+ * - `conflict` — optimistic concurrency lost: a stale `If-Match` validator or
24
+ * Studio `generation` (`409`/`412`). Reload and retry.
25
+ * - `authorization` — the caller is not permitted (`401`/`403`), or the caller
26
+ * tried to override authority locally (a reserved credential/authority
27
+ * header). Never retry with different local state.
28
+ * - `cancelled` — the caller's `AbortSignal` fired.
29
+ * - `transport` — everything else: network failure, unsupported capability,
30
+ * `5xx`, or a missing client the command needs.
31
+ */
32
+ export type HonuaCommandErrorKind = "validation" | "conflict" | "authorization" | "cancelled" | "transport";
33
+ /** One schema or domain validation finding. */
34
+ export interface HonuaCommandValidationIssue {
35
+ /** Dotted path into the input, e.g. `package.sources[0].id`; `""` for the root. */
36
+ readonly path: string;
37
+ readonly message: string;
38
+ }
39
+ /** Constructor options for {@link HonuaCommandError}. */
40
+ export interface HonuaCommandErrorOptions {
41
+ readonly cause?: unknown;
42
+ readonly correlationId?: string;
43
+ readonly idempotencyKey?: string;
44
+ readonly statusCode?: number;
45
+ readonly problem?: HonuaProblemDetails;
46
+ readonly issues?: readonly HonuaCommandValidationIssue[];
47
+ }
48
+ /** Serialization-safe projection of a {@link HonuaCommandError}. */
49
+ export interface SerializedHonuaCommandError {
50
+ readonly kind: "honua.command.error.v1";
51
+ readonly errorKind: HonuaCommandErrorKind;
52
+ readonly commandId: string;
53
+ readonly retryable: boolean;
54
+ readonly correlationId?: string;
55
+ readonly idempotencyKey?: string;
56
+ readonly statusCode?: number;
57
+ readonly issues?: readonly HonuaCommandValidationIssue[];
58
+ }
59
+ /**
60
+ * The single error type every command raises. Callers branch on `.kind`
61
+ * rather than on HTTP status numbers or transport-specific error classes.
62
+ */
63
+ export declare class HonuaCommandError extends Error {
64
+ readonly kind: HonuaCommandErrorKind;
65
+ readonly commandId: string;
66
+ readonly correlationId: string | undefined;
67
+ readonly idempotencyKey: string | undefined;
68
+ readonly statusCode: number | undefined;
69
+ readonly problem: HonuaProblemDetails | undefined;
70
+ readonly issues: readonly HonuaCommandValidationIssue[] | undefined;
71
+ /** `conflict` and `transport` are worth retrying; the rest are terminal. */
72
+ readonly retryable: boolean;
73
+ constructor(kind: HonuaCommandErrorKind, commandId: string, message: string, options?: HonuaCommandErrorOptions);
74
+ /** Safe JSON projection: no message, stack, cause, or response body. */
75
+ toJSON(): SerializedHonuaCommandError;
76
+ }
77
+ /** Type guard for {@link HonuaCommandError}. */
78
+ export declare function isHonuaCommandError(error: unknown): error is HonuaCommandError;
79
+ /** True when the failure is an optimistic-concurrency loss worth reloading and retrying. */
80
+ export declare function isHonuaCommandConflict(error: unknown): error is HonuaCommandError;
81
+ /** Map an HTTP status code onto the command taxonomy. */
82
+ export declare function classifyCommandStatus(statusCode: number): HonuaCommandErrorKind;
83
+ /**
84
+ * Normalize anything thrown beneath a command into a {@link HonuaCommandError}.
85
+ *
86
+ * Recognizes `AbortError`/`AbortSignal.reason` (→ `cancelled`), `HonuaHttpError`
87
+ * and any structurally equivalent error carrying `statusCode` (→ the status
88
+ * classification, which also covers `HonuaStudioError`'s `409`
89
+ * `generation-conflict`), and falls back to `transport`.
90
+ */
91
+ export declare function toHonuaCommandError(error: unknown, commandId: string, context: {
92
+ readonly correlationId: string;
93
+ readonly idempotencyKey: string;
94
+ readonly signal?: AbortSignal;
95
+ }): HonuaCommandError;
96
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/control-plane/commands/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG,UAAU,GAAG,eAAe,GAAG,WAAW,GAAG,WAAW,CAAC;AAE5G,+CAA+C;AAC/C,MAAM,WAAW,2BAA2B;IAC1C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,yDAAyD;AACzD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,2BAA2B,EAAE,CAAC;CAC1D;AAED,oEAAoE;AACpE,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,2BAA2B,EAAE,CAAC;CAC1D;AAID;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAgB,IAAI,EAAE,qBAAqB,CAAC;IAC5C,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClD,SAAgB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnD,SAAgB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,SAAgB,OAAO,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACzD,SAAgB,MAAM,EAAE,SAAS,2BAA2B,EAAE,GAAG,SAAS,CAAC;IAC3E,4EAA4E;IAC5E,SAAgB,SAAS,EAAE,OAAO,CAAC;gBAGjC,IAAI,EAAE,qBAAqB,EAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,wBAA6B;IAcxC,wEAAwE;IACjE,MAAM,IAAI,2BAA2B;CAY7C;AAED,gDAAgD;AAChD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAE9E;AAED,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAEjF;AAED,yDAAyD;AACzD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAK/E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC1G,iBAAiB,CAwBnB"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Typed failure taxonomy for the shared control-plane command layer.
3
+ *
4
+ * Every transport surfaces the *same* five discriminants for the same
5
+ * underlying failure, so a negative RBAC or cross-tenant test written once
6
+ * behaves identically from the CLI, MCP, Studio, and direct JS.
7
+ *
8
+ * Like {@link ../../studio/lifecycle-errors.js | HonuaStudioError} and
9
+ * `HonuaCollaborationError`, this module intentionally does not join the
10
+ * central `HonuaSdkError` registry (`src/core/error-code-registry.ts`):
11
+ * `control-plane` is an `@experimental`, not-yet-semver-covered domain and a
12
+ * registry migration is scoped separately.
13
+ *
14
+ * @experimental
15
+ * @module
16
+ */
17
+ import { HonuaHttpError } from "../../core/errors.js";
18
+ const RETRYABLE_KINDS = new Set(["conflict", "transport"]);
19
+ /**
20
+ * The single error type every command raises. Callers branch on `.kind`
21
+ * rather than on HTTP status numbers or transport-specific error classes.
22
+ */
23
+ export class HonuaCommandError extends Error {
24
+ kind;
25
+ commandId;
26
+ correlationId;
27
+ idempotencyKey;
28
+ statusCode;
29
+ problem;
30
+ issues;
31
+ /** `conflict` and `transport` are worth retrying; the rest are terminal. */
32
+ retryable;
33
+ constructor(kind, commandId, message, options = {}) {
34
+ super(message, "cause" in options ? { cause: options.cause } : undefined);
35
+ this.name = "HonuaCommandError";
36
+ this.kind = kind;
37
+ this.commandId = commandId;
38
+ this.correlationId = options.correlationId;
39
+ this.idempotencyKey = options.idempotencyKey;
40
+ this.statusCode = options.statusCode;
41
+ this.problem = options.problem;
42
+ this.issues = options.issues;
43
+ this.retryable = RETRYABLE_KINDS.has(kind);
44
+ }
45
+ /** Safe JSON projection: no message, stack, cause, or response body. */
46
+ toJSON() {
47
+ return {
48
+ kind: "honua.command.error.v1",
49
+ errorKind: this.kind,
50
+ commandId: this.commandId,
51
+ retryable: this.retryable,
52
+ ...(this.correlationId ? { correlationId: this.correlationId } : {}),
53
+ ...(this.idempotencyKey ? { idempotencyKey: this.idempotencyKey } : {}),
54
+ ...(this.statusCode !== undefined ? { statusCode: this.statusCode } : {}),
55
+ ...(this.issues ? { issues: this.issues } : {}),
56
+ };
57
+ }
58
+ }
59
+ /** Type guard for {@link HonuaCommandError}. */
60
+ export function isHonuaCommandError(error) {
61
+ return error instanceof HonuaCommandError;
62
+ }
63
+ /** True when the failure is an optimistic-concurrency loss worth reloading and retrying. */
64
+ export function isHonuaCommandConflict(error) {
65
+ return isHonuaCommandError(error) && error.kind === "conflict";
66
+ }
67
+ /** Map an HTTP status code onto the command taxonomy. */
68
+ export function classifyCommandStatus(statusCode) {
69
+ if (statusCode === 400 || statusCode === 422)
70
+ return "validation";
71
+ if (statusCode === 401 || statusCode === 403)
72
+ return "authorization";
73
+ if (statusCode === 409 || statusCode === 412 || statusCode === 428)
74
+ return "conflict";
75
+ return "transport";
76
+ }
77
+ /**
78
+ * Normalize anything thrown beneath a command into a {@link HonuaCommandError}.
79
+ *
80
+ * Recognizes `AbortError`/`AbortSignal.reason` (→ `cancelled`), `HonuaHttpError`
81
+ * and any structurally equivalent error carrying `statusCode` (→ the status
82
+ * classification, which also covers `HonuaStudioError`'s `409`
83
+ * `generation-conflict`), and falls back to `transport`.
84
+ */
85
+ export function toHonuaCommandError(error, commandId, context) {
86
+ if (isHonuaCommandError(error))
87
+ return error;
88
+ const base = {
89
+ cause: error,
90
+ correlationId: context.correlationId,
91
+ idempotencyKey: context.idempotencyKey,
92
+ };
93
+ if (isAbortLike(error) || context.signal?.aborted === true) {
94
+ return new HonuaCommandError("cancelled", commandId, `Command ${commandId} was cancelled by the caller.`, base);
95
+ }
96
+ const statusCode = httpStatusOf(error);
97
+ if (statusCode !== undefined) {
98
+ const kind = classifyCommandStatus(statusCode);
99
+ return new HonuaCommandError(kind, commandId, `Command ${commandId} failed with HTTP ${statusCode}.`, {
100
+ ...base,
101
+ statusCode,
102
+ ...(problemOf(error) ? { problem: problemOf(error) } : {}),
103
+ });
104
+ }
105
+ return new HonuaCommandError("transport", commandId, `Command ${commandId} failed before a response was read.`, base);
106
+ }
107
+ function isAbortLike(error) {
108
+ if (typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError")
109
+ return true;
110
+ return error instanceof Error && error.name === "AbortError";
111
+ }
112
+ /**
113
+ * Read an HTTP status off `HonuaHttpError` or any structurally equivalent
114
+ * error. Studio's `HonuaStudioError` is matched structurally on purpose — it
115
+ * ships in a different split package, so `instanceof` is not reliable.
116
+ */
117
+ function httpStatusOf(error) {
118
+ if (error instanceof HonuaHttpError)
119
+ return error.statusCode;
120
+ if (typeof error !== "object" || error === null)
121
+ return undefined;
122
+ const statusCode = error.statusCode;
123
+ return typeof statusCode === "number" && Number.isInteger(statusCode) ? statusCode : undefined;
124
+ }
125
+ function problemOf(error) {
126
+ if (typeof error !== "object" || error === null)
127
+ return undefined;
128
+ const problem = error.problem;
129
+ return typeof problem === "object" && problem !== null ? problem : undefined;
130
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Deterministic identity primitives for command receipts.
3
+ *
4
+ * A receipt has to be reproducible: the same command with the same input and
5
+ * the same tenant must yield the same `idempotencyKey`, `correlationId`, and
6
+ * `auditKey` regardless of which transport issued it, which process it ran in,
7
+ * or when. Nothing here reads the clock, the network, or a random source.
8
+ *
9
+ * The canonical serializer mirrors `canonicalJson` in `src/offline/digest.ts`
10
+ * (sorted own keys, no `undefined` members); it is redeclared rather than
11
+ * imported so the control-plane split package does not depend on the offline
12
+ * module. The hash is a 128-bit FNV-1a variant: an identity key, deliberately
13
+ * **not** a security primitive — nothing authorizes on it.
14
+ *
15
+ * @experimental
16
+ * @module
17
+ */
18
+ import type { HonuaCommandIdentity, HonuaCommandReceipt } from "./types.js";
19
+ /**
20
+ * Serialize `value` with sorted own keys, dropping `undefined` members.
21
+ *
22
+ * Dropping (rather than throwing on) `undefined` matches how the command layer
23
+ * builds inputs: optional fields are routinely absent, and an absent field and
24
+ * an explicit `undefined` must hash the same so a CLI adapter that always sets
25
+ * every flag key agrees with a JS caller that omits them.
26
+ */
27
+ export declare function canonicalCommandJson(value: unknown): string;
28
+ /**
29
+ * 128-bit FNV-1a over UTF-16 code units, rendered as 32 lowercase hex digits.
30
+ *
31
+ * Four independently seeded 32-bit lanes. Stable across engines and platforms
32
+ * because it never touches `TextEncoder`, `crypto`, or locale-sensitive APIs.
33
+ */
34
+ export declare function commandDigest(text: string): string;
35
+ /**
36
+ * Derive the `Idempotency-Key` for a command invocation.
37
+ *
38
+ * Bound to the command id, the canonicalized input, and the tenant echo — so
39
+ * the *same* logical request retried from any transport collapses to one
40
+ * server-side effect, while the same input under a different tenant does not.
41
+ */
42
+ export declare function deriveIdempotencyKey(commandId: string, input: unknown, identity: HonuaCommandIdentity): string;
43
+ /**
44
+ * Derive a correlation id from the idempotency key.
45
+ *
46
+ * Deterministic on purpose: a random trace id would make receipts differ
47
+ * between transports and break the audit join the issue asks for. Callers that
48
+ * already own a trace id pass it in and this is not used.
49
+ */
50
+ export declare function deriveCorrelationId(commandId: string, idempotencyKey: string): string;
51
+ /** Normalize a claimed identity: trimmed values, sorted deduplicated scopes, no empty keys. */
52
+ export declare function normalizeCommandIdentity(identity: HonuaCommandIdentity | undefined): HonuaCommandIdentity;
53
+ /**
54
+ * The transport-independent projection of a receipt.
55
+ *
56
+ * Everything a server-side audit join needs, minus the one field that is
57
+ * allowed to differ between two equivalent calls (`transport`) and minus the
58
+ * `auditKey` itself.
59
+ */
60
+ export declare function honuaCommandReceiptProjection(receipt: HonuaCommandReceipt): Record<string, unknown>;
61
+ /**
62
+ * Stable join key for a receipt. Equal for equivalent CLI, MCP, Studio, and JS
63
+ * calls; changes whenever any recorded field of the invocation changes.
64
+ */
65
+ export declare function honuaCommandAuditKey(receipt: HonuaCommandReceipt): string;
66
+ /**
67
+ * Canonical, byte-stable serialization of a receipt — sorted keys, no
68
+ * `undefined`. Use this when persisting a receipt or comparing two of them.
69
+ */
70
+ export declare function serializeHonuaCommandReceipt(receipt: HonuaCommandReceipt): string;
71
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../../../src/control-plane/commands/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAU5E;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAY3D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,oBAAoB,GAAG,MAAM,CAO9G;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAErF;AAED,+FAA+F;AAC/F,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,SAAS,GAAG,oBAAoB,CAUzG;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGnG;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAEzE;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAEjF"}