@honua/react 0.1.7-beta.0 → 0.1.10-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.
- package/control-plane/admin-client.d.ts +89 -0
- package/control-plane/admin-client.d.ts.map +1 -0
- package/control-plane/admin-client.js +200 -0
- package/control-plane/client.d.ts +1 -1
- package/control-plane/client.d.ts.map +1 -1
- package/control-plane/client.js +145 -11
- package/control-plane/commands/catalog.d.ts +133 -0
- package/control-plane/commands/catalog.d.ts.map +1 -0
- package/control-plane/commands/catalog.js +365 -0
- package/control-plane/commands/errors.d.ts +96 -0
- package/control-plane/commands/errors.d.ts.map +1 -0
- package/control-plane/commands/errors.js +130 -0
- package/control-plane/commands/identity.d.ts +71 -0
- package/control-plane/commands/identity.d.ts.map +1 -0
- package/control-plane/commands/identity.js +0 -0
- package/control-plane/commands/index.d.ts +39 -0
- package/control-plane/commands/index.d.ts.map +1 -0
- package/control-plane/commands/index.js +34 -0
- package/control-plane/commands/runtime.d.ts +146 -0
- package/control-plane/commands/runtime.d.ts.map +1 -0
- package/control-plane/commands/runtime.js +330 -0
- package/control-plane/commands/types.d.ts +228 -0
- package/control-plane/commands/types.d.ts.map +1 -0
- package/control-plane/commands/types.js +30 -0
- package/control-plane/commands/validate.d.ts +27 -0
- package/control-plane/commands/validate.d.ts.map +1 -0
- package/control-plane/commands/validate.js +132 -0
- package/control-plane/generated/admin-api.d.ts +30265 -0
- package/control-plane/generated/admin-api.d.ts.map +1 -0
- package/control-plane/generated/admin-api.js +5 -0
- package/control-plane/generated/admin-operations.d.ts +4053 -0
- package/control-plane/generated/admin-operations.d.ts.map +1 -0
- package/control-plane/generated/admin-operations.js +820 -0
- package/control-plane/index.d.ts +11 -0
- package/control-plane/index.d.ts.map +1 -1
- package/control-plane/index.js +8 -0
- package/control-plane/types.d.ts +14 -0
- package/control-plane/types.d.ts.map +1 -1
- package/core/client.d.ts.map +1 -1
- package/core/client.js +29 -31
- package/core/credential-redaction.d.ts +163 -0
- package/core/credential-redaction.d.ts.map +1 -0
- package/core/credential-redaction.js +389 -0
- package/core/error-classifications.d.ts +15 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +15 -0
- package/core/error-code-registry.d.ts +15 -0
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +15 -0
- package/core/error-envelope.d.ts +31 -0
- package/core/error-envelope.d.ts.map +1 -1
- package/core/error-envelope.js +11 -0
- package/core/errors.d.ts +15 -3
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +254 -2
- package/core/geometry-input-sr.d.ts +3 -0
- package/core/geometry-input-sr.d.ts.map +1 -0
- package/core/geometry-input-sr.js +28 -0
- package/core/geoservices.d.ts.map +1 -1
- package/core/geoservices.js +33 -6
- package/core/grpc-adapter.d.ts +2 -2
- package/core/grpc-adapter.d.ts.map +1 -1
- package/core/grpc-adapter.js +41 -9
- package/core/job-run-errors.d.ts +10 -0
- package/core/job-run-errors.d.ts.map +1 -0
- package/core/job-run-errors.js +14 -0
- package/core/job-run-lifecycle.d.ts +34 -0
- package/core/job-run-lifecycle.d.ts.map +1 -0
- package/core/job-run-lifecycle.js +193 -0
- package/core/ogc-processes.d.ts +38 -31
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +123 -173
- package/core/pbf-decoder.d.ts.map +1 -1
- package/core/pbf-decoder.js +16 -4
- package/core/process-runner.js +15 -100
- package/core/protocol-transport.d.ts +6 -1
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/request-pipeline.d.ts +6 -3
- package/core/request-pipeline.d.ts.map +1 -1
- package/core/request-pipeline.js +28 -11
- package/core/spatial-filter.d.ts +4 -4
- package/core/spatial-filter.d.ts.map +1 -1
- package/core/spatial-filter.js +7 -7
- package/core/surfaces.d.ts +2 -8
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +21 -112
- package/core/types.d.ts +21 -9
- package/core/types.d.ts.map +1 -1
- package/esri-compat/class-breaks-renderer.d.ts +2 -0
- package/esri-compat/class-breaks-renderer.d.ts.map +1 -1
- package/esri-compat/class-breaks-renderer.js +7 -0
- package/esri-compat/controls.d.ts +10 -1
- package/esri-compat/controls.d.ts.map +1 -1
- package/esri-compat/controls.js +86 -2
- package/esri-compat/directions.d.ts +35 -0
- package/esri-compat/directions.d.ts.map +1 -1
- package/esri-compat/directions.js +153 -2
- package/esri-compat/expand.d.ts +1 -0
- package/esri-compat/expand.d.ts.map +1 -1
- package/esri-compat/expand.js +61 -0
- package/esri-compat/feature-layer.d.ts +29 -4
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +335 -13
- package/esri-compat/geometry-engine.d.ts +4 -0
- package/esri-compat/geometry-engine.d.ts.map +1 -1
- package/esri-compat/geometry-engine.js +8 -1
- package/esri-compat/graphic.d.ts +7 -4
- package/esri-compat/graphic.d.ts.map +1 -1
- package/esri-compat/legend.d.ts +2 -1
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +15 -0
- package/esri-compat/map-view-mount.d.ts +48 -0
- package/esri-compat/map-view-mount.d.ts.map +1 -0
- package/esri-compat/map-view-mount.js +344 -0
- package/esri-compat/map-view.d.ts +38 -6
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +467 -33
- package/esri-compat/portal.d.ts +2 -0
- package/esri-compat/portal.d.ts.map +1 -1
- package/esri-compat/portal.js +7 -2
- package/esri-compat/renderer-objects.d.ts +7 -2
- package/esri-compat/renderer-objects.d.ts.map +1 -1
- package/esri-compat/renderer-objects.js +16 -5
- package/esri-compat/route-layer.d.ts +8 -0
- package/esri-compat/route-layer.d.ts.map +1 -1
- package/esri-compat/route-layer.js +24 -4
- package/esri-compat/route-task.d.ts +5 -1
- package/esri-compat/route-task.d.ts.map +1 -1
- package/esri-compat/route-task.js +119 -7
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/scene-view.js +1 -0
- package/esri-compat/search.d.ts +2 -1
- package/esri-compat/search.d.ts.map +1 -1
- package/esri-compat/search.js +52 -2
- package/esri-compat/unique-value-renderer.d.ts +2 -0
- package/esri-compat/unique-value-renderer.d.ts.map +1 -1
- package/esri-compat/unique-value-renderer.js +7 -0
- package/esri-compat/web-map.d.ts +10 -0
- package/esri-compat/web-map.d.ts.map +1 -1
- package/esri-compat/web-map.js +102 -0
- package/gen/geospatial/v1/common_pb.d.ts +514 -0
- package/gen/geospatial/v1/common_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/common_pb.js +335 -0
- package/gen/geospatial/v1/execution_types_pb.d.ts +1186 -0
- package/gen/geospatial/v1/execution_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/execution_types_pb.js +396 -0
- package/gen/geospatial/v1/feature_service_pb.d.ts +403 -0
- package/gen/geospatial/v1/feature_service_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/feature_service_pb.js +47 -0
- package/gen/geospatial/v1/index.d.ts +5 -0
- package/gen/geospatial/v1/index.d.ts.map +1 -0
- package/gen/geospatial/v1/index.js +9 -0
- package/gen/geospatial/v1/spatial_types_pb.d.ts +285 -0
- package/gen/geospatial/v1/spatial_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/spatial_types_pb.js +58 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts +379 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.js +151 -0
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/projection.js +10 -1
- package/map/webmap-maplibre.d.ts +2 -1
- package/map/webmap-maplibre.d.ts.map +1 -1
- package/map/webmap-maplibre.js +3 -0
- package/package.json +3 -3
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +2 -0
- package/query-planner/grpc.d.ts +4 -4
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +11 -11
- package/query-planner/types.d.ts +6 -4
- package/query-planner/types.d.ts.map +1 -1
- package/realtime/honua-server.d.ts +4 -0
- package/realtime/honua-server.d.ts.map +1 -1
- package/realtime/honua-server.js +10 -0
- package/realtime/resumable.js +9 -0
- package/realtime/types.d.ts +8 -0
- package/realtime/types.d.ts.map +1 -1
- package/runtime/errors.d.ts +1 -1
- package/runtime/errors.d.ts.map +1 -1
- package/runtime/errors.js +2 -0
- package/runtime/generated/map-package-schema-meta.d.ts +11 -0
- package/runtime/generated/map-package-schema-meta.d.ts.map +1 -0
- package/runtime/generated/map-package-schema-meta.js +12 -0
- package/runtime/generated/map-package-schema-validator.d.ts +16 -0
- package/runtime/generated/map-package-schema-validator.d.ts.map +1 -0
- package/runtime/generated/map-package-schema-validator.js +1422 -0
- package/runtime/index.d.ts +5 -1
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +6 -0
- package/runtime/map-package-export.d.ts +151 -0
- package/runtime/map-package-export.d.ts.map +1 -0
- package/runtime/map-package-export.js +636 -0
- package/runtime/map-package-validation.d.ts +21 -1
- package/runtime/map-package-validation.d.ts.map +1 -1
- package/runtime/map-package-validation.js +132 -95
- package/runtime/map-package.d.ts +80 -0
- package/runtime/map-package.d.ts.map +1 -1
- package/studio/capability-manifest.d.ts +10 -0
- package/studio/capability-manifest.d.ts.map +1 -1
- package/studio/capability-manifest.js +38 -0
- package/studio/chart-spec.d.ts +6 -6
- package/studio/chart-spec.d.ts.map +1 -1
- package/studio/chart-spec.js +6 -6
- package/studio/command-adapter.d.ts +35 -0
- package/studio/command-adapter.d.ts.map +1 -0
- package/studio/command-adapter.js +36 -0
- package/studio/generated/studio-api.d.ts +1062 -0
- package/studio/generated/studio-api.d.ts.map +1 -0
- package/studio/generated/studio-api.js +4 -0
- package/studio/index.d.ts +25 -13
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -10
- package/studio/lifecycle-client.d.ts +262 -22
- package/studio/lifecycle-client.d.ts.map +1 -1
- package/studio/lifecycle-client.js +543 -24
- package/studio/lifecycle-errors.d.ts +8 -2
- package/studio/lifecycle-errors.d.ts.map +1 -1
- package/studio/lifecycle-errors.js +6 -1
- package/studio/lifecycle-types.d.ts +237 -38
- package/studio/lifecycle-types.d.ts.map +1 -1
- package/studio/lifecycle-types.js +5 -12
- package/studio/publication-status.d.ts +55 -0
- package/studio/publication-status.d.ts.map +1 -0
- package/studio/publication-status.js +103 -0
- package/studio/types.d.ts +37 -7
- package/studio/types.d.ts.map +1 -1
- package/studio/types.js +5 -3
- package/studio/validate.d.ts.map +1 -1
- package/studio/validate.js +104 -7
- package/style/renderers.d.ts +6 -1
- package/style/renderers.d.ts.map +1 -1
- package/style/renderers.js +11 -6
- package/style/visual-variables.d.ts +43 -0
- package/style/visual-variables.d.ts.map +1 -0
- package/style/visual-variables.js +183 -0
- package/webmap/convert-layer.d.ts +2 -1
- package/webmap/convert-layer.d.ts.map +1 -1
- package/webmap/convert-layer.js +20 -3
- package/webmap/convert-renderer.d.ts +10 -3
- package/webmap/convert-renderer.d.ts.map +1 -1
- package/webmap/convert-renderer.js +190 -36
- package/webmap/index.d.ts +1 -0
- package/webmap/index.d.ts.map +1 -1
- package/webmap/parse.d.ts +2 -1
- package/webmap/parse.d.ts.map +1 -1
- package/webmap/parse.js +1 -1
- package/gen/honua/v1/feature_service_pb.d.ts +0 -918
- package/gen/honua/v1/feature_service_pb.d.ts.map +0 -1
- package/gen/honua/v1/feature_service_pb.js +0 -375
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place a command actually runs.
|
|
3
|
+
*
|
|
4
|
+
* `HonuaCommandRuntime.execute` owns everything that must be identical across
|
|
5
|
+
* transports: authority-header refusal, schema validation, idempotency-key and
|
|
6
|
+
* correlation-id derivation, dry-run short-circuiting, cancellation, error
|
|
7
|
+
* normalization, and receipt assembly. A transport contributes argument
|
|
8
|
+
* parsing on the way in and rendering on the way out — nothing else.
|
|
9
|
+
*
|
|
10
|
+
* ## Security contract
|
|
11
|
+
*
|
|
12
|
+
* - **No shared administrator credential.** The runtime is built from a
|
|
13
|
+
* {@link HonuaControlPlaneClient} (and optionally a Studio lifecycle client),
|
|
14
|
+
* both of which carry the *caller's own* credential from the `HonuaClient`
|
|
15
|
+
* they were constructed with. There is no admin-key path here and no
|
|
16
|
+
* `HonuaAdminClient` dependency.
|
|
17
|
+
* - **No client-side authorization bypass.** Caller-supplied per-call headers
|
|
18
|
+
* are screened against {@link HONUA_COMMAND_RESERVED_HEADERS}; any attempt to
|
|
19
|
+
* set a credential, actor, tenant, policy, or approver header fails with an
|
|
20
|
+
* `authorization` {@link HonuaCommandError} *before* the command runs, on
|
|
21
|
+
* every transport. The claimed {@link HonuaCommandIdentity} is echoed onto
|
|
22
|
+
* the receipt and never placed on the wire.
|
|
23
|
+
* - **No caller-owned request identity.** The same screen refuses
|
|
24
|
+
* {@link HONUA_COMMAND_OWNED_HEADERS} — the `Idempotency-Key`, `If-Match`, and `X-Correlation-ID`
|
|
25
|
+
* the runtime derives and records on the receipt. Both screens compare
|
|
26
|
+
* header names case-insensitively, so `Idempotency-Key`, `idempotency-key`,
|
|
27
|
+
* and `IDEMPOTENCY-KEY` are the same header to them. Without this a caller
|
|
28
|
+
* could put one key on the wire while the receipt and the invocation record
|
|
29
|
+
* claimed another, breaking retry collapsing and the audit join.
|
|
30
|
+
* - **No self-approval.** Command input schemas are closed, so no transport can
|
|
31
|
+
* introduce an approval field the others lack; every receipt records
|
|
32
|
+
* `authorization: "server-enforced"`.
|
|
33
|
+
* - **No credential in the receipt's rendered fields.** A receipt is built to be
|
|
34
|
+
* persisted and printed, so the plan's human-readable `summary` goes through
|
|
35
|
+
* the SDK's one credential recognizer before it is recorded — an import's
|
|
36
|
+
* presigned `sourceUrl` must not outlive its request in an audit log.
|
|
37
|
+
*
|
|
38
|
+
* @experimental
|
|
39
|
+
* @module
|
|
40
|
+
*/
|
|
41
|
+
import { redactHonuaExportText } from "../../core/credential-redaction.js";
|
|
42
|
+
import { HonuaControlPlaneClient } from "../client.js";
|
|
43
|
+
import { HonuaCommandError, toHonuaCommandError } from "./errors.js";
|
|
44
|
+
import { canonicalCommandJson, commandDigest, deriveCorrelationId, deriveIdempotencyKey, normalizeCommandIdentity, } from "./identity.js";
|
|
45
|
+
import { HONUA_COMMAND_RECEIPT_KIND, HONUA_COMMAND_TRANSPORTS, } from "./types.js";
|
|
46
|
+
import { validateCommandInput } from "./validate.js";
|
|
47
|
+
/**
|
|
48
|
+
* Headers a caller may never set on a command invocation.
|
|
49
|
+
*
|
|
50
|
+
* Two families: credential headers (which would let a transport swap in a
|
|
51
|
+
* different — possibly shared administrator — identity) and authority headers
|
|
52
|
+
* (which would let a transport assert an actor, tenant, policy decision, or
|
|
53
|
+
* approval the server did not derive itself). Compared case-insensitively.
|
|
54
|
+
*/
|
|
55
|
+
export const HONUA_COMMAND_RESERVED_HEADERS = [
|
|
56
|
+
"authorization",
|
|
57
|
+
"proxy-authorization",
|
|
58
|
+
"cookie",
|
|
59
|
+
"x-api-key",
|
|
60
|
+
"x-honua-api-key",
|
|
61
|
+
"x-honua-admin-key",
|
|
62
|
+
"x-honua-actor",
|
|
63
|
+
"x-honua-act-as",
|
|
64
|
+
"x-honua-impersonate",
|
|
65
|
+
"x-honua-tenant",
|
|
66
|
+
"x-honua-tenant-id",
|
|
67
|
+
"x-honua-scope",
|
|
68
|
+
"x-honua-scopes",
|
|
69
|
+
"x-honua-policy-decision",
|
|
70
|
+
"x-honua-approver",
|
|
71
|
+
"x-honua-approved-by",
|
|
72
|
+
"x-forwarded-user",
|
|
73
|
+
"x-forwarded-access-token",
|
|
74
|
+
];
|
|
75
|
+
const RESERVED_HEADER_SET = new Set(HONUA_COMMAND_RESERVED_HEADERS);
|
|
76
|
+
/**
|
|
77
|
+
* Headers the runtime derives and records on the receipt.
|
|
78
|
+
*
|
|
79
|
+
* A caller-supplied value would put a different key on the wire
|
|
80
|
+
* than the receipt claims, which breaks exactly the two guarantees the receipt
|
|
81
|
+
* exists to provide: that a retry collapses to one server-side effect, and that
|
|
82
|
+
* an audit join can match a receipt to the request it describes. Callers set
|
|
83
|
+
* these through {@link HonuaCommandInvocation.idempotencyKey},
|
|
84
|
+
* {@link HonuaCommandInvocation.ifMatch}, and {@link HonuaCommandInvocation.correlationId}, which are recorded.
|
|
85
|
+
*/
|
|
86
|
+
export const HONUA_COMMAND_OWNED_HEADERS = ["idempotency-key", "if-match", "x-correlation-id"];
|
|
87
|
+
const OWNED_HEADER_SET = new Set(HONUA_COMMAND_OWNED_HEADERS);
|
|
88
|
+
/** Build a {@link HonuaCommandRuntime}. */
|
|
89
|
+
export function createHonuaCommandRuntime(options) {
|
|
90
|
+
return new HonuaCommandRuntime(options);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Executes {@link HonuaCommand}s. One instance per connection; share it across
|
|
94
|
+
* every transport a process hosts.
|
|
95
|
+
*
|
|
96
|
+
* @experimental
|
|
97
|
+
*/
|
|
98
|
+
export class HonuaCommandRuntime {
|
|
99
|
+
#controlPlane;
|
|
100
|
+
#studio;
|
|
101
|
+
#identity;
|
|
102
|
+
constructor(options) {
|
|
103
|
+
const controlPlane = options.controlPlane ?? (options.client ? new HonuaControlPlaneClient({ client: options.client }) : undefined);
|
|
104
|
+
if (!controlPlane) {
|
|
105
|
+
throw new TypeError("createHonuaCommandRuntime requires either a `client` or a prebuilt `controlPlane`.");
|
|
106
|
+
}
|
|
107
|
+
this.#controlPlane = controlPlane;
|
|
108
|
+
this.#studio = options.studio;
|
|
109
|
+
this.#identity = normalizeCommandIdentity(options.identity);
|
|
110
|
+
}
|
|
111
|
+
get controlPlane() {
|
|
112
|
+
return this.#controlPlane;
|
|
113
|
+
}
|
|
114
|
+
get studio() {
|
|
115
|
+
return this.#studio;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Validate, plan, and (unless `dryRun`) execute `command`, returning a
|
|
119
|
+
* deterministic {@link HonuaCommandReceipt}.
|
|
120
|
+
*
|
|
121
|
+
* @throws {HonuaCommandError} for every failure mode, already classified.
|
|
122
|
+
*/
|
|
123
|
+
async execute(command, input, invocation) {
|
|
124
|
+
const transport = assertTransport(command.id, invocation.transport);
|
|
125
|
+
const identity = normalizeCommandIdentity({ ...this.#identity, ...invocation.identity });
|
|
126
|
+
const idempotencyKey = invocation.idempotencyKey?.trim() || deriveIdempotencyKey(command.id, input, identity);
|
|
127
|
+
const correlationId = invocation.correlationId?.trim() || deriveCorrelationId(command.id, idempotencyKey);
|
|
128
|
+
const failureContext = { correlationId, idempotencyKey, signal: invocation.signal };
|
|
129
|
+
assertNoAuthorityOverride(command.id, invocation.headers, { correlationId, idempotencyKey });
|
|
130
|
+
assertNoCommandKeyOverride(command.id, invocation.headers, { correlationId, idempotencyKey });
|
|
131
|
+
// Match the server's CorrelationIdMiddleware contract. An unsupported id
|
|
132
|
+
// would be silently replaced there, leaving the receipt unable to join it.
|
|
133
|
+
if (!/^[A-Za-z0-9_.:-]{1,64}$/.test(correlationId)) {
|
|
134
|
+
throw new HonuaCommandError("validation", command.id, "Command correlationId must contain 1–64 ASCII letters, digits, hyphens, underscores, dots, or colons.", {
|
|
135
|
+
...failureContext,
|
|
136
|
+
issues: [{ path: "correlationId", message: "must match the server correlation-id format" }],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Schema first, then the command's own cross-field rules. Both run before
|
|
140
|
+
// `plan`, so the dry-run preview below is reachable only for input the real
|
|
141
|
+
// invocation would also accept.
|
|
142
|
+
const issues = [...validateCommandInput(command.inputSchema, input), ...(command.validate?.(input) ?? [])];
|
|
143
|
+
if (issues.length > 0) {
|
|
144
|
+
throw new HonuaCommandError("validation", command.id, `Command ${command.id} received invalid input: ${issues.map((issue) => `${issue.path || "(root)"} ${issue.message}`).join("; ")}.`, { ...failureContext, issues });
|
|
145
|
+
}
|
|
146
|
+
if (invocation.signal?.aborted) {
|
|
147
|
+
throw new HonuaCommandError("cancelled", command.id, `Command ${command.id} was cancelled before it started.`, {
|
|
148
|
+
...failureContext,
|
|
149
|
+
cause: invocation.signal.reason,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const context = {
|
|
153
|
+
input,
|
|
154
|
+
controlPlane: this.#controlPlane,
|
|
155
|
+
...(this.#studio ? { studio: this.#studio } : {}),
|
|
156
|
+
identity,
|
|
157
|
+
transport,
|
|
158
|
+
idempotencyKey,
|
|
159
|
+
correlationId,
|
|
160
|
+
dryRun: invocation.dryRun === true,
|
|
161
|
+
...(invocation.signal ? { signal: invocation.signal } : {}),
|
|
162
|
+
requestOptions: (overrides = {}) => {
|
|
163
|
+
const headers = new Headers(overrides.headers ?? invocation.headers);
|
|
164
|
+
headers.set("X-Correlation-ID", correlationId);
|
|
165
|
+
return {
|
|
166
|
+
...(invocation.signal ? { signal: invocation.signal } : {}),
|
|
167
|
+
...(command.mode === "action" ? { idempotencyKey } : {}),
|
|
168
|
+
...(invocation.ifMatch ? { ifMatch: invocation.ifMatch } : {}),
|
|
169
|
+
...overrides,
|
|
170
|
+
headers,
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
let plan;
|
|
175
|
+
try {
|
|
176
|
+
plan = withoutResourceLink(command.plan(context));
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw toHonuaCommandError(error, command.id, failureContext);
|
|
180
|
+
}
|
|
181
|
+
if (context.dryRun) {
|
|
182
|
+
return buildReceipt({
|
|
183
|
+
command,
|
|
184
|
+
status: "dry-run",
|
|
185
|
+
idempotencyKey,
|
|
186
|
+
correlationId,
|
|
187
|
+
identity,
|
|
188
|
+
transport,
|
|
189
|
+
plan,
|
|
190
|
+
...(plan.resourceRef ? { resourceRef: plan.resourceRef } : {}),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const outcome = await command.execute(context);
|
|
195
|
+
return buildReceipt({
|
|
196
|
+
command,
|
|
197
|
+
status: outcome.status ?? "ok",
|
|
198
|
+
idempotencyKey,
|
|
199
|
+
correlationId,
|
|
200
|
+
identity,
|
|
201
|
+
transport,
|
|
202
|
+
plan,
|
|
203
|
+
...((outcome.resourceRef ?? plan.resourceRef) ? { resourceRef: outcome.resourceRef ?? plan.resourceRef } : {}),
|
|
204
|
+
...(outcome.validators ? { validators: outcome.validators } : {}),
|
|
205
|
+
...(outcome.problem ? { problem: outcome.problem } : {}),
|
|
206
|
+
output: outcome.output,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
throw toHonuaCommandError(error, command.id, failureContext);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Refuse a caller-supplied credential or authority header.
|
|
216
|
+
*
|
|
217
|
+
* Exported so a transport can screen its own header pass-through before it
|
|
218
|
+
* builds an invocation, but the runtime applies it unconditionally — a
|
|
219
|
+
* transport that forgets cannot weaken the guarantee.
|
|
220
|
+
*/
|
|
221
|
+
export function assertNoAuthorityOverride(commandId, headers, context = {}) {
|
|
222
|
+
const offending = headerNames(headers).filter((name) => RESERVED_HEADER_SET.has(name));
|
|
223
|
+
if (offending.length === 0)
|
|
224
|
+
return;
|
|
225
|
+
const named = offending.sort().join(", ");
|
|
226
|
+
throw new HonuaCommandError("authorization", commandId, `Command ${commandId} refused caller-supplied authority header(s): ${named}. Authorization is derived server-side from the connection credential; no transport may assert it.`, context);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Refuse a caller-supplied value for a header the runtime derives.
|
|
230
|
+
*
|
|
231
|
+
* Classified `validation` rather than `authorization`: this is caller misuse of
|
|
232
|
+
* the invocation contract, not an attempt to assert authority. Use
|
|
233
|
+
* {@link HonuaCommandInvocation.idempotencyKey} / `.ifMatch` instead, so the
|
|
234
|
+
* value that travels is the value the receipt records.
|
|
235
|
+
*/
|
|
236
|
+
export function assertNoCommandKeyOverride(commandId, headers, context = {}) {
|
|
237
|
+
const offending = headerNames(headers).filter((name) => OWNED_HEADER_SET.has(name));
|
|
238
|
+
if (offending.length === 0)
|
|
239
|
+
return;
|
|
240
|
+
const named = offending.sort().join(", ");
|
|
241
|
+
throw new HonuaCommandError("validation", commandId, `Command ${commandId} refused caller-supplied header(s) it derives itself: ${named}. Set them through the invocation's \`idempotencyKey\` / \`ifMatch\` / \`correlationId\` so the value on the wire is the value the receipt records.`, {
|
|
242
|
+
...context,
|
|
243
|
+
issues: offending.sort().map((name) => ({
|
|
244
|
+
path: `headers.${name}`,
|
|
245
|
+
message: "is derived by the command runtime and cannot be set by a caller",
|
|
246
|
+
})),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Strip the resource link from a plan.
|
|
251
|
+
*
|
|
252
|
+
* A plan describes a request that has *not been issued* — and on a dry run it
|
|
253
|
+
* is the whole receipt, because `execute` is never reached. A link on it would
|
|
254
|
+
* therefore be a URL for a resource that may not exist, printed by the CLI and
|
|
255
|
+
* Studio next to the word "dry run", which is precisely honua-sdk-js#1426's
|
|
256
|
+
* "preview is never reported as persisted or published" failing. `plan` is
|
|
257
|
+
* already documented to carry no request body for a related reason; carrying no
|
|
258
|
+
* resource link is the same rule applied to the response side.
|
|
259
|
+
*
|
|
260
|
+
* The executed path is unaffected: a real invocation reports the `href` the
|
|
261
|
+
* *server* returned, on `HonuaCommandOutcome.resourceRef`. What a command can
|
|
262
|
+
* no longer do is predict one.
|
|
263
|
+
*/
|
|
264
|
+
function withoutResourceLink(plan) {
|
|
265
|
+
const ref = plan.resourceRef;
|
|
266
|
+
if (!ref || ref.href === undefined)
|
|
267
|
+
return plan;
|
|
268
|
+
const { href: _href, ...rest } = ref;
|
|
269
|
+
return { ...plan, resourceRef: rest };
|
|
270
|
+
}
|
|
271
|
+
function headerNames(headers) {
|
|
272
|
+
if (!headers)
|
|
273
|
+
return [];
|
|
274
|
+
if (headers instanceof Headers)
|
|
275
|
+
return [...headers.keys()].map((name) => name.toLowerCase());
|
|
276
|
+
if (Array.isArray(headers))
|
|
277
|
+
return headers.map(([name]) => String(name).toLowerCase());
|
|
278
|
+
return Object.keys(headers).map((name) => name.toLowerCase());
|
|
279
|
+
}
|
|
280
|
+
function assertTransport(commandId, transport) {
|
|
281
|
+
if (HONUA_COMMAND_TRANSPORTS.includes(transport))
|
|
282
|
+
return transport;
|
|
283
|
+
throw new HonuaCommandError("validation", commandId, `Unknown command transport "${String(transport)}"; expected one of ${HONUA_COMMAND_TRANSPORTS.join(", ")}.`);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Withhold credential material from the plan's human-readable summary.
|
|
287
|
+
*
|
|
288
|
+
* {@link HonuaCommandPlan} promises it "carries no request body: command inputs
|
|
289
|
+
* can contain connection credentials, and a plan is rendered to terminals and
|
|
290
|
+
* logs". Omitting the body is not on its own enough: a command builds its
|
|
291
|
+
* `summary` from its own input, and an import's `sourceUrl` is routinely a
|
|
292
|
+
* presigned URL whose query string *is* the credential. Interpolated into a
|
|
293
|
+
* receipt — which `serializeHonuaCommandReceipt` exists to persist, and which
|
|
294
|
+
* `honua map publish --json` prints verbatim — that URL outlives the request it
|
|
295
|
+
* authorized, in a log the caller believes is safe to keep.
|
|
296
|
+
*
|
|
297
|
+
* So the summary goes through the SDK's one credential recognizer
|
|
298
|
+
* (`src/core/credential-redaction.ts`) on the way onto the receipt. The
|
|
299
|
+
* substitution is deterministic, so two equivalent calls still agree on
|
|
300
|
+
* `auditKey`. `path` is deliberately left alone: it is documented as the
|
|
301
|
+
* request exactly as issued, is built only from percent-encoded resource ids,
|
|
302
|
+
* and redacting an id would misreport the request. `output` is left alone too
|
|
303
|
+
* — it is the command's return value, not a rendered preview.
|
|
304
|
+
*/
|
|
305
|
+
function redactPlanSummary(plan) {
|
|
306
|
+
const summary = redactHonuaExportText(plan.summary);
|
|
307
|
+
return summary === plan.summary ? plan : { ...plan, summary };
|
|
308
|
+
}
|
|
309
|
+
function buildReceipt(parts) {
|
|
310
|
+
const projection = {
|
|
311
|
+
kind: HONUA_COMMAND_RECEIPT_KIND,
|
|
312
|
+
commandId: parts.command.id,
|
|
313
|
+
mode: parts.command.mode,
|
|
314
|
+
status: parts.status,
|
|
315
|
+
...(parts.command.mode === "action" ? { idempotencyKey: parts.idempotencyKey } : {}),
|
|
316
|
+
correlationId: parts.correlationId,
|
|
317
|
+
identity: parts.identity,
|
|
318
|
+
authorization: "server-enforced",
|
|
319
|
+
plan: redactPlanSummary(parts.plan),
|
|
320
|
+
...(parts.resourceRef ? { resourceRef: parts.resourceRef } : {}),
|
|
321
|
+
...(parts.validators ? { validators: parts.validators } : {}),
|
|
322
|
+
...(parts.problem ? { problem: parts.problem } : {}),
|
|
323
|
+
...(parts.output !== undefined ? { output: parts.output } : {}),
|
|
324
|
+
};
|
|
325
|
+
return {
|
|
326
|
+
...projection,
|
|
327
|
+
transport: parts.transport,
|
|
328
|
+
auditKey: commandDigest(canonicalCommandJson(projection)),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared application-command contract for the Honua control plane.
|
|
3
|
+
*
|
|
4
|
+
* Every surface — the `honua` CLI, the MCP server, Studio, and direct JS —
|
|
5
|
+
* invokes the *same* {@link HonuaCommand} objects. A transport adapts input
|
|
6
|
+
* and output only: it parses its own argument shape into the command's
|
|
7
|
+
* declared JSON-schema input, hands the result to
|
|
8
|
+
* `HonuaCommandRuntime.execute`, and renders the returned
|
|
9
|
+
* {@link HonuaCommandReceipt}. Domain sequencing (read-modify-write,
|
|
10
|
+
* optimistic-concurrency checks, job hand-off) and the dry-run projection live
|
|
11
|
+
* on the command, never on the transport.
|
|
12
|
+
*
|
|
13
|
+
* Authorization is deliberately **not** modelled here beyond an echo. The
|
|
14
|
+
* command layer carries no administrator credential of its own, refuses
|
|
15
|
+
* caller-supplied authority headers and the request-identity headers it derives
|
|
16
|
+
* itself (see `assertNoAuthorityOverride` and `assertNoCommandKeyOverride` in
|
|
17
|
+
* `./runtime.js`), and stamps every receipt `authorization:
|
|
18
|
+
* "server-enforced"`. A client cannot approve its own publication by choosing
|
|
19
|
+
* a different transport because no transport can express approval at all —
|
|
20
|
+
* the shared command inputs have no approval field and every command input
|
|
21
|
+
* schema is closed (`additionalProperties: false`).
|
|
22
|
+
*
|
|
23
|
+
* @experimental Part of the `@honua/sdk-js/control-plane` experimental
|
|
24
|
+
* entrypoint; not yet covered by the SDK's semver contract.
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
import type { QueryMethod } from "../../core/types.js";
|
|
28
|
+
import type { HonuaStudioLifecycleClient } from "../../studio/lifecycle-client.js";
|
|
29
|
+
import type { HonuaControlPlaneClient } from "../client.js";
|
|
30
|
+
import type { HonuaControlPlaneRequestOptions, HonuaEntityValidators, HonuaProblemDetails } from "../types.js";
|
|
31
|
+
import type { HonuaCommandValidationIssue } from "./errors.js";
|
|
32
|
+
/**
|
|
33
|
+
* JSON-Schema subset used to describe a command's input.
|
|
34
|
+
*
|
|
35
|
+
* Structurally the same dialect `HonuaAgentToolDefinition.inputSchema` uses
|
|
36
|
+
* (`src/agent-tools/index.ts`) so MCP tool schemas, CLI help, and Studio forms
|
|
37
|
+
* can all project from one description. It is redeclared here rather than
|
|
38
|
+
* imported so the control-plane split package does not pull in the agent-tool
|
|
39
|
+
* runtime. `src/` is deliberately zod-free.
|
|
40
|
+
*/
|
|
41
|
+
export interface HonuaCommandJsonSchema {
|
|
42
|
+
readonly type?: string | readonly string[];
|
|
43
|
+
readonly description?: string;
|
|
44
|
+
readonly enum?: readonly unknown[];
|
|
45
|
+
readonly format?: string;
|
|
46
|
+
readonly properties?: Readonly<Record<string, HonuaCommandJsonSchema>>;
|
|
47
|
+
readonly items?: HonuaCommandJsonSchema;
|
|
48
|
+
readonly required?: readonly string[];
|
|
49
|
+
readonly additionalProperties?: boolean | HonuaCommandJsonSchema;
|
|
50
|
+
readonly minLength?: number;
|
|
51
|
+
readonly maxLength?: number;
|
|
52
|
+
readonly minimum?: number;
|
|
53
|
+
readonly maximum?: number;
|
|
54
|
+
readonly default?: unknown;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `read` commands never mutate and never carry an idempotency key on the
|
|
58
|
+
* wire; `action` commands do both. Mirrors `HonuaAgentToolMode`.
|
|
59
|
+
*/
|
|
60
|
+
export type HonuaCommandMode = "read" | "action";
|
|
61
|
+
/**
|
|
62
|
+
* Terminal state recorded on a {@link HonuaCommandReceipt}. Mirrors
|
|
63
|
+
* `HonuaAgentToolStatus` and adds `cancelled` for `AbortSignal` aborts.
|
|
64
|
+
*/
|
|
65
|
+
export type HonuaCommandStatus = "ok" | "dry-run" | "denied" | "cancelled" | "error";
|
|
66
|
+
/** Surfaces that may invoke a command. Recorded, never trusted. */
|
|
67
|
+
export declare const HONUA_COMMAND_TRANSPORTS: readonly ["cli", "mcp", "studio", "sdk"];
|
|
68
|
+
/** One of {@link HONUA_COMMAND_TRANSPORTS}. */
|
|
69
|
+
export type HonuaCommandTransport = (typeof HONUA_COMMAND_TRANSPORTS)[number];
|
|
70
|
+
/**
|
|
71
|
+
* Acting identity and tenant, as the *caller claims them*.
|
|
72
|
+
*
|
|
73
|
+
* These values are echoed onto the receipt so a downstream audit join can line
|
|
74
|
+
* a receipt up with the server-side record. They are never sent as request
|
|
75
|
+
* headers and never used to make a local authorization decision — the server
|
|
76
|
+
* derives the real acting identity from the credential on the
|
|
77
|
+
* {@link HonuaControlPlaneClient}'s underlying `HonuaClient`.
|
|
78
|
+
*/
|
|
79
|
+
export interface HonuaCommandIdentity {
|
|
80
|
+
/** Acting principal id as claimed by the caller. */
|
|
81
|
+
readonly actor?: string;
|
|
82
|
+
/** Tenant / workspace-owner echo. */
|
|
83
|
+
readonly tenantId?: string;
|
|
84
|
+
/** OAuth scope ceiling the caller believes it holds, sorted for determinism. */
|
|
85
|
+
readonly scopes?: readonly string[];
|
|
86
|
+
}
|
|
87
|
+
/** Stable pointer to the resource a command read or affected. */
|
|
88
|
+
export interface HonuaCommandResourceRef {
|
|
89
|
+
/** Resource kind, e.g. `map-package`, `import-job`, `connection`. */
|
|
90
|
+
readonly type: string;
|
|
91
|
+
/** Server-assigned identifier, when the server returned one. */
|
|
92
|
+
readonly id?: string;
|
|
93
|
+
/** Owning workspace, when scoped. */
|
|
94
|
+
readonly workspaceId?: string;
|
|
95
|
+
/** Absolute or root-relative link the server returned for the resource. */
|
|
96
|
+
readonly href?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The request a command *would* issue. Produced by {@link HonuaCommand.plan},
|
|
100
|
+
* recorded on every receipt, and the only thing a dry run evaluates — a dry
|
|
101
|
+
* run never reaches {@link HonuaCommand.execute}, so no transport can
|
|
102
|
+
* accidentally execute while previewing.
|
|
103
|
+
*
|
|
104
|
+
* Deliberately carries no request body: command inputs can contain connection
|
|
105
|
+
* credentials, and a plan is rendered to terminals and logs.
|
|
106
|
+
*
|
|
107
|
+
* It also carries no **resource link**. `resourceRef.href` is dropped by the
|
|
108
|
+
* runtime before the plan reaches a receipt, so a dry run — which is nothing
|
|
109
|
+
* but this plan — cannot print a URL for a resource that was never created or
|
|
110
|
+
* published (honua-sdk-js#1426). A link appears on a receipt only when the
|
|
111
|
+
* *server* returned one to {@link HonuaCommand.execute}.
|
|
112
|
+
*/
|
|
113
|
+
export interface HonuaCommandPlan {
|
|
114
|
+
readonly method: QueryMethod;
|
|
115
|
+
/** Path relative to the client's base path, exactly as the command will call it. */
|
|
116
|
+
readonly path: string;
|
|
117
|
+
/**
|
|
118
|
+
* One-line human summary for CLI/Studio previews.
|
|
119
|
+
*
|
|
120
|
+
* A command builds this from its own input, so it can name a source URL. The
|
|
121
|
+
* runtime passes it through the SDK's credential recognizer before it reaches
|
|
122
|
+
* a receipt, so a presigned URL or an embedded secret is withheld rather than
|
|
123
|
+
* persisted into an audit record. Commands should still prefer identifiers to
|
|
124
|
+
* URLs here.
|
|
125
|
+
*/
|
|
126
|
+
readonly summary: string;
|
|
127
|
+
/** Resource this request addresses. Any `href` is stripped by the runtime. */
|
|
128
|
+
readonly resourceRef?: HonuaCommandResourceRef;
|
|
129
|
+
}
|
|
130
|
+
/** What {@link HonuaCommand.execute} returns to the runtime. */
|
|
131
|
+
export interface HonuaCommandOutcome<TOutput> {
|
|
132
|
+
readonly output: TOutput;
|
|
133
|
+
/** Overrides `ok`; use `denied` when the server durably recorded a refusal. */
|
|
134
|
+
readonly status?: Extract<HonuaCommandStatus, "ok" | "denied">;
|
|
135
|
+
readonly resourceRef?: HonuaCommandResourceRef;
|
|
136
|
+
readonly validators?: HonuaEntityValidators;
|
|
137
|
+
readonly problem?: HonuaProblemDetails;
|
|
138
|
+
}
|
|
139
|
+
/** Discriminating tag on {@link HonuaCommandReceipt}. */
|
|
140
|
+
export declare const HONUA_COMMAND_RECEIPT_KIND: "honua.command.receipt.v1";
|
|
141
|
+
/**
|
|
142
|
+
* Deterministic, serializable record of one command invocation.
|
|
143
|
+
*
|
|
144
|
+
* Two equivalent calls made from different transports produce identical
|
|
145
|
+
* receipts apart from {@link HonuaCommandReceipt.transport}; `auditKey` hashes
|
|
146
|
+
* the transport-independent projection so a server-side audit join can match
|
|
147
|
+
* them. Nothing here is time-, host-, or random-dependent: `idempotencyKey`
|
|
148
|
+
* and `correlationId` are derived from the command id, the canonicalized
|
|
149
|
+
* input, and the tenant unless the caller supplies them. Read commands omit
|
|
150
|
+
* `idempotencyKey` because no such key is sent to the server.
|
|
151
|
+
*/
|
|
152
|
+
export interface HonuaCommandReceipt<TOutput = unknown> {
|
|
153
|
+
readonly kind: typeof HONUA_COMMAND_RECEIPT_KIND;
|
|
154
|
+
readonly commandId: string;
|
|
155
|
+
readonly mode: HonuaCommandMode;
|
|
156
|
+
readonly status: HonuaCommandStatus;
|
|
157
|
+
readonly idempotencyKey?: string;
|
|
158
|
+
/** Sent as `X-Correlation-ID` on every request belonging to this command. */
|
|
159
|
+
readonly correlationId: string;
|
|
160
|
+
readonly identity: HonuaCommandIdentity;
|
|
161
|
+
/** Surface that issued the call. Recorded for audit; excluded from `auditKey`. */
|
|
162
|
+
readonly transport: HonuaCommandTransport;
|
|
163
|
+
/** Always `server-enforced`: the command layer makes no authorization decision. */
|
|
164
|
+
readonly authorization: "server-enforced";
|
|
165
|
+
readonly plan: HonuaCommandPlan;
|
|
166
|
+
readonly resourceRef?: HonuaCommandResourceRef;
|
|
167
|
+
readonly validators?: HonuaEntityValidators;
|
|
168
|
+
readonly problem?: HonuaProblemDetails;
|
|
169
|
+
readonly output?: TOutput;
|
|
170
|
+
/** Hash of the transport-independent projection. Equal across transports. */
|
|
171
|
+
readonly auditKey: string;
|
|
172
|
+
}
|
|
173
|
+
/** Per-invocation context handed to {@link HonuaCommand.plan} and `execute`. */
|
|
174
|
+
export interface HonuaCommandContext<TInput> {
|
|
175
|
+
/** Input after schema validation. */
|
|
176
|
+
readonly input: TInput;
|
|
177
|
+
readonly controlPlane: HonuaControlPlaneClient;
|
|
178
|
+
/**
|
|
179
|
+
* Studio lifecycle client, present only when the host supplied one. Commands
|
|
180
|
+
* in the `studio.*` namespace throw a `transport` `HonuaCommandError` when it
|
|
181
|
+
* is absent rather than reaching for a second credential.
|
|
182
|
+
*/
|
|
183
|
+
readonly studio?: HonuaStudioLifecycleClient;
|
|
184
|
+
readonly identity: HonuaCommandIdentity;
|
|
185
|
+
readonly transport: HonuaCommandTransport;
|
|
186
|
+
readonly idempotencyKey: string;
|
|
187
|
+
readonly correlationId: string;
|
|
188
|
+
readonly dryRun: boolean;
|
|
189
|
+
readonly signal?: AbortSignal;
|
|
190
|
+
/**
|
|
191
|
+
* Request options for the underlying resource client, with `Idempotency-Key`
|
|
192
|
+
* (action commands only), `If-Match`, the caller's `AbortSignal`, and the
|
|
193
|
+
* caller's remaining headers already threaded. The headers that reach here
|
|
194
|
+
* can carry neither an authority claim nor a command-owned key: both
|
|
195
|
+
* families are refused before `plan` runs.
|
|
196
|
+
*/
|
|
197
|
+
requestOptions(overrides?: HonuaControlPlaneRequestOptions): HonuaControlPlaneRequestOptions;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* One idempotent application command, shared verbatim by every transport.
|
|
201
|
+
*
|
|
202
|
+
* @typeParam TInput - Validated input shape, described by `inputSchema`.
|
|
203
|
+
* @typeParam TOutput - Value carried on the receipt's `output`.
|
|
204
|
+
*/
|
|
205
|
+
export interface HonuaCommand<TInput, TOutput> {
|
|
206
|
+
/** Stable dotted id, e.g. `map-package.publish`. */
|
|
207
|
+
readonly id: string;
|
|
208
|
+
readonly title: string;
|
|
209
|
+
readonly description: string;
|
|
210
|
+
readonly mode: HonuaCommandMode;
|
|
211
|
+
/** Resource kind this command addresses; used for the receipt's `resourceRef`. */
|
|
212
|
+
readonly resourceKind: string;
|
|
213
|
+
readonly inputSchema: HonuaCommandJsonSchema;
|
|
214
|
+
/**
|
|
215
|
+
* Cross-field checks the JSON schema cannot express ("one of these two is
|
|
216
|
+
* required"). Called by the runtime after schema validation and **before**
|
|
217
|
+
* `plan`, so a dry run can never approve an input the real invocation would
|
|
218
|
+
* reject. Must be pure. Return an empty array when the input is valid.
|
|
219
|
+
*/
|
|
220
|
+
validate?(input: TInput): readonly HonuaCommandValidationIssue[];
|
|
221
|
+
/** Describe the request without issuing it. Must be pure and side-effect free. */
|
|
222
|
+
plan(context: HonuaCommandContext<TInput>): HonuaCommandPlan;
|
|
223
|
+
/** Issue the request. Never called during a dry run. */
|
|
224
|
+
execute(context: HonuaCommandContext<TInput>): Promise<HonuaCommandOutcome<TOutput>>;
|
|
225
|
+
}
|
|
226
|
+
/** Any command, for registries and transport dispatch tables. */
|
|
227
|
+
export type HonuaAnyCommand = HonuaCommand<any, any>;
|
|
228
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/control-plane/commands/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,+BAA+B,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC/G,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,sBAAsB,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjD;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAErF,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,0CAA2C,CAAC;AAEjF,+CAA+C;AAC/C,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,iEAAiE;AACjE,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,uBAAuB,CAAC;CAChD;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAmB,CAAC,OAAO;IAC1C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,EAAE,IAAI,GAAG,QAAQ,CAAC,CAAC;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,uBAAuB,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CACxC;AAED,yDAAyD;AACzD,eAAO,MAAM,0BAA0B,EAAG,0BAAmC,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB,CAAC,OAAO,GAAG,OAAO;IACpD,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;IAC1C,mFAAmF;IACnF,QAAQ,CAAC,aAAa,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,uBAAuB,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB,CAAC,MAAM;IACzC,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;IAC1C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;;;OAMG;IACH,cAAc,CAAC,SAAS,CAAC,EAAE,+BAA+B,GAAG,+BAA+B,CAAC;CAC9F;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,MAAM,EAAE,OAAO;IAC3C,oDAAoD;IACpD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,kFAAkF;IAClF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,2BAA2B,EAAE,CAAC;IACjE,kFAAkF;IAClF,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,MAAM,CAAC,GAAG,gBAAgB,CAAC;IAC7D,wDAAwD;IACxD,OAAO,CAAC,OAAO,EAAE,mBAAmB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;CACtF;AAED,iEAAiE;AAEjE,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared application-command contract for the Honua control plane.
|
|
3
|
+
*
|
|
4
|
+
* Every surface — the `honua` CLI, the MCP server, Studio, and direct JS —
|
|
5
|
+
* invokes the *same* {@link HonuaCommand} objects. A transport adapts input
|
|
6
|
+
* and output only: it parses its own argument shape into the command's
|
|
7
|
+
* declared JSON-schema input, hands the result to
|
|
8
|
+
* `HonuaCommandRuntime.execute`, and renders the returned
|
|
9
|
+
* {@link HonuaCommandReceipt}. Domain sequencing (read-modify-write,
|
|
10
|
+
* optimistic-concurrency checks, job hand-off) and the dry-run projection live
|
|
11
|
+
* on the command, never on the transport.
|
|
12
|
+
*
|
|
13
|
+
* Authorization is deliberately **not** modelled here beyond an echo. The
|
|
14
|
+
* command layer carries no administrator credential of its own, refuses
|
|
15
|
+
* caller-supplied authority headers and the request-identity headers it derives
|
|
16
|
+
* itself (see `assertNoAuthorityOverride` and `assertNoCommandKeyOverride` in
|
|
17
|
+
* `./runtime.js`), and stamps every receipt `authorization:
|
|
18
|
+
* "server-enforced"`. A client cannot approve its own publication by choosing
|
|
19
|
+
* a different transport because no transport can express approval at all —
|
|
20
|
+
* the shared command inputs have no approval field and every command input
|
|
21
|
+
* schema is closed (`additionalProperties: false`).
|
|
22
|
+
*
|
|
23
|
+
* @experimental Part of the `@honua/sdk-js/control-plane` experimental
|
|
24
|
+
* entrypoint; not yet covered by the SDK's semver contract.
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
/** Surfaces that may invoke a command. Recorded, never trusted. */
|
|
28
|
+
export const HONUA_COMMAND_TRANSPORTS = ["cli", "mcp", "studio", "sdk"];
|
|
29
|
+
/** Discriminating tag on {@link HonuaCommandReceipt}. */
|
|
30
|
+
export const HONUA_COMMAND_RECEIPT_KIND = "honua.command.receipt.v1";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural validation for command inputs.
|
|
3
|
+
*
|
|
4
|
+
* Commands describe their input with the JSON-Schema subset in `./types.js`,
|
|
5
|
+
* the same dialect `src/agent-tools/index.ts` uses for agent tool definitions.
|
|
6
|
+
* That one description drives CLI argument checking, MCP tool schemas, Studio
|
|
7
|
+
* forms, and this validator — so an input rejected by one transport is
|
|
8
|
+
* rejected identically by all of them.
|
|
9
|
+
*
|
|
10
|
+
* The validator covers exactly the keywords the dialect declares. It is not a
|
|
11
|
+
* general JSON-Schema implementation and deliberately adds no dependency;
|
|
12
|
+
* `src/` is zod-free.
|
|
13
|
+
*
|
|
14
|
+
* @experimental
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import type { HonuaCommandValidationIssue } from "./errors.js";
|
|
18
|
+
import type { HonuaCommandJsonSchema } from "./types.js";
|
|
19
|
+
/**
|
|
20
|
+
* Validate `value` against `schema`, returning every finding.
|
|
21
|
+
*
|
|
22
|
+
* `additionalProperties: false` is the load-bearing rule for the security
|
|
23
|
+
* contract: a closed input schema is why no transport can smuggle an approval
|
|
24
|
+
* or authority field into a shared command.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateCommandInput(schema: HonuaCommandJsonSchema, value: unknown): HonuaCommandValidationIssue[];
|
|
27
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/control-plane/commands/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,EAAE,KAAK,EAAE,OAAO,GAAG,2BAA2B,EAAE,CAIlH"}
|