@honua/react 0.1.7-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.
- package/control-plane/admin-client.d.ts +87 -0
- package/control-plane/admin-client.d.ts.map +1 -0
- package/control-plane/admin-client.js +169 -0
- package/control-plane/client.d.ts.map +1 -1
- package/control-plane/client.js +21 -3
- 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 +353 -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 +318 -0
- package/control-plane/commands/types.d.ts +226 -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 +30263 -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.js +1 -1
- 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/grpc-adapter.d.ts +2 -2
- package/core/grpc-adapter.d.ts.map +1 -1
- package/core/grpc-adapter.js +30 -6
- 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/process-runner.js +15 -100
- 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 +7 -6
- package/core/types.d.ts.map +1 -1
- 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/package.json +2 -2
- 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 +4 -4
- package/query-planner/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/chart-spec.d.ts +6 -6
- package/studio/chart-spec.d.ts.map +1 -1
- package/studio/chart-spec.js +6 -6
- package/studio/index.d.ts +17 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +15 -5
- package/studio/lifecycle-client.d.ts +262 -22
- package/studio/lifecycle-client.d.ts.map +1 -1
- package/studio/lifecycle-client.js +524 -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 +228 -8
- package/studio/lifecycle-types.d.ts.map +1 -1
- 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/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,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, idempotent application commands for the Honua control plane.
|
|
3
|
+
*
|
|
4
|
+
* One typed command layer behind every surface. The CLI, the MCP server,
|
|
5
|
+
* Studio, and direct JS all call {@link HonuaCommandRuntime.execute} with the
|
|
6
|
+
* same {@link HonuaCommand} objects and get back the same deterministic
|
|
7
|
+
* {@link HonuaCommandReceipt}; transports adapt input and output only.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import {
|
|
11
|
+
* createHonuaCommandRuntime,
|
|
12
|
+
* mapPackagePublishCommand,
|
|
13
|
+
* } from "@honua/sdk-js/control-plane";
|
|
14
|
+
*
|
|
15
|
+
* const runtime = createHonuaCommandRuntime({ client });
|
|
16
|
+
* const receipt = await runtime.execute(
|
|
17
|
+
* mapPackagePublishCommand,
|
|
18
|
+
* { mapId: "map-ops", package: mapPackage },
|
|
19
|
+
* { transport: "sdk", identity: { actor: "user-1", tenantId: "acme" } },
|
|
20
|
+
* );
|
|
21
|
+
* receipt.auditKey; // identical for the equivalent `honua map publish` call
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @experimental Reachable from the experimental
|
|
25
|
+
* `@honua/sdk-js/control-plane` entrypoint; not yet covered by the SDK's
|
|
26
|
+
* semver contract.
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
export { HONUA_COMMANDS, HONUA_COMMAND_IDS, connectionTestCommand, importCreateCommand, isHonuaCommandId, mapPackagePublishCommand, studioDraftSaveVersionCommand, } from "./catalog.js";
|
|
30
|
+
export type { ConnectionTestInput, ConnectionTestOutput, HonuaCommandId, ImportCreateInput, MapPackagePublishInput, StudioDraftSaveVersionInput, } from "./catalog.js";
|
|
31
|
+
export { HonuaCommandError, classifyCommandStatus, isHonuaCommandConflict, isHonuaCommandError, toHonuaCommandError, } from "./errors.js";
|
|
32
|
+
export type { HonuaCommandErrorKind, HonuaCommandErrorOptions, HonuaCommandValidationIssue, SerializedHonuaCommandError, } from "./errors.js";
|
|
33
|
+
export { canonicalCommandJson, commandDigest, deriveCorrelationId, deriveIdempotencyKey, honuaCommandAuditKey, honuaCommandReceiptProjection, normalizeCommandIdentity, serializeHonuaCommandReceipt, } from "./identity.js";
|
|
34
|
+
export { HONUA_COMMAND_OWNED_HEADERS, HONUA_COMMAND_RESERVED_HEADERS, HonuaCommandRuntime, assertNoAuthorityOverride, assertNoCommandKeyOverride, createHonuaCommandRuntime, } from "./runtime.js";
|
|
35
|
+
export type { HonuaCommandInvocation, HonuaCommandRuntimeOptions } from "./runtime.js";
|
|
36
|
+
export { HONUA_COMMAND_RECEIPT_KIND, HONUA_COMMAND_TRANSPORTS } from "./types.js";
|
|
37
|
+
export type { HonuaAnyCommand, HonuaCommand, HonuaCommandContext, HonuaCommandIdentity, HonuaCommandJsonSchema, HonuaCommandMode, HonuaCommandOutcome, HonuaCommandPlan, HonuaCommandReceipt, HonuaCommandResourceRef, HonuaCommandStatus, HonuaCommandTransport, } from "./types.js";
|
|
38
|
+
export { validateCommandInput } from "./validate.js";
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/control-plane/commands/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,6BAA6B,EAC7B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,EAC9B,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAClF,YAAY,EACV,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, idempotent application commands for the Honua control plane.
|
|
3
|
+
*
|
|
4
|
+
* One typed command layer behind every surface. The CLI, the MCP server,
|
|
5
|
+
* Studio, and direct JS all call {@link HonuaCommandRuntime.execute} with the
|
|
6
|
+
* same {@link HonuaCommand} objects and get back the same deterministic
|
|
7
|
+
* {@link HonuaCommandReceipt}; transports adapt input and output only.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import {
|
|
11
|
+
* createHonuaCommandRuntime,
|
|
12
|
+
* mapPackagePublishCommand,
|
|
13
|
+
* } from "@honua/sdk-js/control-plane";
|
|
14
|
+
*
|
|
15
|
+
* const runtime = createHonuaCommandRuntime({ client });
|
|
16
|
+
* const receipt = await runtime.execute(
|
|
17
|
+
* mapPackagePublishCommand,
|
|
18
|
+
* { mapId: "map-ops", package: mapPackage },
|
|
19
|
+
* { transport: "sdk", identity: { actor: "user-1", tenantId: "acme" } },
|
|
20
|
+
* );
|
|
21
|
+
* receipt.auditKey; // identical for the equivalent `honua map publish` call
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @experimental Reachable from the experimental
|
|
25
|
+
* `@honua/sdk-js/control-plane` entrypoint; not yet covered by the SDK's
|
|
26
|
+
* semver contract.
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
export { HONUA_COMMANDS, HONUA_COMMAND_IDS, connectionTestCommand, importCreateCommand, isHonuaCommandId, mapPackagePublishCommand, studioDraftSaveVersionCommand, } from "./catalog.js";
|
|
30
|
+
export { HonuaCommandError, classifyCommandStatus, isHonuaCommandConflict, isHonuaCommandError, toHonuaCommandError, } from "./errors.js";
|
|
31
|
+
export { canonicalCommandJson, commandDigest, deriveCorrelationId, deriveIdempotencyKey, honuaCommandAuditKey, honuaCommandReceiptProjection, normalizeCommandIdentity, serializeHonuaCommandReceipt, } from "./identity.js";
|
|
32
|
+
export { HONUA_COMMAND_OWNED_HEADERS, HONUA_COMMAND_RESERVED_HEADERS, HonuaCommandRuntime, assertNoAuthorityOverride, assertNoCommandKeyOverride, createHonuaCommandRuntime, } from "./runtime.js";
|
|
33
|
+
export { HONUA_COMMAND_RECEIPT_KIND, HONUA_COMMAND_TRANSPORTS } from "./types.js";
|
|
34
|
+
export { validateCommandInput } from "./validate.js";
|
|
@@ -0,0 +1,146 @@
|
|
|
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` and `If-Match`
|
|
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 type { HonuaClient } from "../../core/client.js";
|
|
42
|
+
import type { HonuaStudioLifecycleClient } from "../../studio/lifecycle-client.js";
|
|
43
|
+
import { HonuaControlPlaneClient } from "../client.js";
|
|
44
|
+
import { type HonuaCommand, type HonuaCommandIdentity, type HonuaCommandReceipt, type HonuaCommandTransport } from "./types.js";
|
|
45
|
+
/**
|
|
46
|
+
* Headers a caller may never set on a command invocation.
|
|
47
|
+
*
|
|
48
|
+
* Two families: credential headers (which would let a transport swap in a
|
|
49
|
+
* different — possibly shared administrator — identity) and authority headers
|
|
50
|
+
* (which would let a transport assert an actor, tenant, policy decision, or
|
|
51
|
+
* approval the server did not derive itself). Compared case-insensitively.
|
|
52
|
+
*/
|
|
53
|
+
export declare const HONUA_COMMAND_RESERVED_HEADERS: readonly ["authorization", "proxy-authorization", "cookie", "x-api-key", "x-honua-api-key", "x-honua-admin-key", "x-honua-actor", "x-honua-act-as", "x-honua-impersonate", "x-honua-tenant", "x-honua-tenant-id", "x-honua-scope", "x-honua-scopes", "x-honua-policy-decision", "x-honua-approver", "x-honua-approved-by", "x-forwarded-user", "x-forwarded-access-token"];
|
|
54
|
+
/**
|
|
55
|
+
* Headers the runtime derives and records on the receipt.
|
|
56
|
+
*
|
|
57
|
+
* A caller-supplied value for either one would put a different key on the wire
|
|
58
|
+
* than the receipt claims, which breaks exactly the two guarantees the receipt
|
|
59
|
+
* exists to provide: that a retry collapses to one server-side effect, and that
|
|
60
|
+
* an audit join can match a receipt to the request it describes. Callers set
|
|
61
|
+
* these through {@link HonuaCommandInvocation.idempotencyKey} and
|
|
62
|
+
* {@link HonuaCommandInvocation.ifMatch}, which are recorded.
|
|
63
|
+
*/
|
|
64
|
+
export declare const HONUA_COMMAND_OWNED_HEADERS: readonly ["idempotency-key", "if-match"];
|
|
65
|
+
/** Options accepted by every {@link HonuaCommandRuntime.execute} call. */
|
|
66
|
+
export interface HonuaCommandInvocation {
|
|
67
|
+
/** Surface issuing the call. Recorded on the receipt; never trusted. */
|
|
68
|
+
readonly transport: HonuaCommandTransport;
|
|
69
|
+
/** Acting identity and tenant echo. Never sent as headers. */
|
|
70
|
+
readonly identity?: HonuaCommandIdentity;
|
|
71
|
+
/** Explicit `Idempotency-Key`; derived deterministically from the input when omitted. */
|
|
72
|
+
readonly idempotencyKey?: string;
|
|
73
|
+
/** Explicit correlation id; derived from the idempotency key when omitted. */
|
|
74
|
+
readonly correlationId?: string;
|
|
75
|
+
/** Preview only — the runtime returns the plan and never calls `execute`. */
|
|
76
|
+
readonly dryRun?: boolean;
|
|
77
|
+
/** Optimistic-concurrency validator, sent as `If-Match`. */
|
|
78
|
+
readonly ifMatch?: string;
|
|
79
|
+
/** Cancellation. An abort surfaces as a `cancelled` {@link HonuaCommandError}. */
|
|
80
|
+
readonly signal?: AbortSignal;
|
|
81
|
+
/** Extra request headers. Reserved credential/authority headers are refused. */
|
|
82
|
+
readonly headers?: HeadersInit;
|
|
83
|
+
}
|
|
84
|
+
/** Constructor options for {@link createHonuaCommandRuntime}. */
|
|
85
|
+
export interface HonuaCommandRuntimeOptions {
|
|
86
|
+
/**
|
|
87
|
+
* Transport client. Its credential *is* the acting identity — the runtime
|
|
88
|
+
* never adds one of its own.
|
|
89
|
+
*/
|
|
90
|
+
readonly client?: HonuaClient;
|
|
91
|
+
/** Pre-built control-plane client; constructed from `client` when omitted. */
|
|
92
|
+
readonly controlPlane?: HonuaControlPlaneClient;
|
|
93
|
+
/**
|
|
94
|
+
* Studio lifecycle client. Supplied by the host because Studio ships in a
|
|
95
|
+
* different split package; `studio.*` commands fail with a `transport` error
|
|
96
|
+
* when it is absent.
|
|
97
|
+
*/
|
|
98
|
+
readonly studio?: HonuaStudioLifecycleClient;
|
|
99
|
+
/** Default identity echo, overridable per invocation. */
|
|
100
|
+
readonly identity?: HonuaCommandIdentity;
|
|
101
|
+
}
|
|
102
|
+
/** Build a {@link HonuaCommandRuntime}. */
|
|
103
|
+
export declare function createHonuaCommandRuntime(options: HonuaCommandRuntimeOptions): HonuaCommandRuntime;
|
|
104
|
+
/**
|
|
105
|
+
* Executes {@link HonuaCommand}s. One instance per connection; share it across
|
|
106
|
+
* every transport a process hosts.
|
|
107
|
+
*
|
|
108
|
+
* @experimental
|
|
109
|
+
*/
|
|
110
|
+
export declare class HonuaCommandRuntime {
|
|
111
|
+
#private;
|
|
112
|
+
constructor(options: HonuaCommandRuntimeOptions);
|
|
113
|
+
get controlPlane(): HonuaControlPlaneClient;
|
|
114
|
+
get studio(): HonuaStudioLifecycleClient | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Validate, plan, and (unless `dryRun`) execute `command`, returning a
|
|
117
|
+
* deterministic {@link HonuaCommandReceipt}.
|
|
118
|
+
*
|
|
119
|
+
* @throws {HonuaCommandError} for every failure mode, already classified.
|
|
120
|
+
*/
|
|
121
|
+
execute<TInput, TOutput>(command: HonuaCommand<TInput, TOutput>, input: TInput, invocation: HonuaCommandInvocation): Promise<HonuaCommandReceipt<TOutput>>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Refuse a caller-supplied credential or authority header.
|
|
125
|
+
*
|
|
126
|
+
* Exported so a transport can screen its own header pass-through before it
|
|
127
|
+
* builds an invocation, but the runtime applies it unconditionally — a
|
|
128
|
+
* transport that forgets cannot weaken the guarantee.
|
|
129
|
+
*/
|
|
130
|
+
export declare function assertNoAuthorityOverride(commandId: string, headers: HeadersInit | undefined, context?: {
|
|
131
|
+
readonly correlationId?: string;
|
|
132
|
+
readonly idempotencyKey?: string;
|
|
133
|
+
}): void;
|
|
134
|
+
/**
|
|
135
|
+
* Refuse a caller-supplied value for a header the runtime derives.
|
|
136
|
+
*
|
|
137
|
+
* Classified `validation` rather than `authorization`: this is caller misuse of
|
|
138
|
+
* the invocation contract, not an attempt to assert authority. Use
|
|
139
|
+
* {@link HonuaCommandInvocation.idempotencyKey} / `.ifMatch` instead, so the
|
|
140
|
+
* value that travels is the value the receipt records.
|
|
141
|
+
*/
|
|
142
|
+
export declare function assertNoCommandKeyOverride(commandId: string, headers: HeadersInit | undefined, context?: {
|
|
143
|
+
readonly correlationId?: string;
|
|
144
|
+
readonly idempotencyKey?: string;
|
|
145
|
+
}): void;
|
|
146
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/control-plane/commands/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAUvD,OAAO,EAGL,KAAK,YAAY,EAEjB,KAAK,oBAAoB,EAEzB,KAAK,mBAAmB,EAExB,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AAGpB;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,4WAmBjC,CAAC;AAIX;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,0CAA2C,CAAC;AAIpF,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;IAC1C,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IACzC,yFAAyF;IACzF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAChC;AAED,iEAAiE;AACjE,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAC;IAC7C,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CAC1C;AAED,2CAA2C;AAC3C,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,mBAAmB,CAElG;AAED;;;;;GAKG;AACH,qBAAa,mBAAmB;;gBAKX,OAAO,EAAE,0BAA0B;IAWtD,IAAW,YAAY,IAAI,uBAAuB,CAEjD;IAED,IAAW,MAAM,IAAI,0BAA0B,GAAG,SAAS,CAE1D;IAED;;;;;OAKG;IACU,OAAO,CAAC,MAAM,EAAE,OAAO,EAClC,OAAO,EAAE,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;CAwFzC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,OAAO,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAClF,IAAI,CAUN;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,OAAO,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAClF,IAAI,CAgBN"}
|
|
@@ -0,0 +1,318 @@
|
|
|
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` and `If-Match`
|
|
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 for either one 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} and
|
|
84
|
+
* {@link HonuaCommandInvocation.ifMatch}, which are recorded.
|
|
85
|
+
*/
|
|
86
|
+
export const HONUA_COMMAND_OWNED_HEADERS = ["idempotency-key", "if-match"];
|
|
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
|
+
// Schema first, then the command's own cross-field rules. Both run before
|
|
132
|
+
// `plan`, so the dry-run preview below is reachable only for input the real
|
|
133
|
+
// invocation would also accept.
|
|
134
|
+
const issues = [...validateCommandInput(command.inputSchema, input), ...(command.validate?.(input) ?? [])];
|
|
135
|
+
if (issues.length > 0) {
|
|
136
|
+
throw new HonuaCommandError("validation", command.id, `Command ${command.id} received invalid input: ${issues.map((issue) => `${issue.path || "(root)"} ${issue.message}`).join("; ")}.`, { ...failureContext, issues });
|
|
137
|
+
}
|
|
138
|
+
if (invocation.signal?.aborted) {
|
|
139
|
+
throw new HonuaCommandError("cancelled", command.id, `Command ${command.id} was cancelled before it started.`, {
|
|
140
|
+
...failureContext,
|
|
141
|
+
cause: invocation.signal.reason,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const context = {
|
|
145
|
+
input,
|
|
146
|
+
controlPlane: this.#controlPlane,
|
|
147
|
+
...(this.#studio ? { studio: this.#studio } : {}),
|
|
148
|
+
identity,
|
|
149
|
+
transport,
|
|
150
|
+
idempotencyKey,
|
|
151
|
+
correlationId,
|
|
152
|
+
dryRun: invocation.dryRun === true,
|
|
153
|
+
...(invocation.signal ? { signal: invocation.signal } : {}),
|
|
154
|
+
requestOptions: (overrides = {}) => ({
|
|
155
|
+
...(invocation.signal ? { signal: invocation.signal } : {}),
|
|
156
|
+
...(command.mode === "action" ? { idempotencyKey } : {}),
|
|
157
|
+
...(invocation.ifMatch ? { ifMatch: invocation.ifMatch } : {}),
|
|
158
|
+
...(invocation.headers ? { headers: invocation.headers } : {}),
|
|
159
|
+
...overrides,
|
|
160
|
+
}),
|
|
161
|
+
};
|
|
162
|
+
let plan;
|
|
163
|
+
try {
|
|
164
|
+
plan = withoutResourceLink(command.plan(context));
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
throw toHonuaCommandError(error, command.id, failureContext);
|
|
168
|
+
}
|
|
169
|
+
if (context.dryRun) {
|
|
170
|
+
return buildReceipt({
|
|
171
|
+
command,
|
|
172
|
+
status: "dry-run",
|
|
173
|
+
idempotencyKey,
|
|
174
|
+
correlationId,
|
|
175
|
+
identity,
|
|
176
|
+
transport,
|
|
177
|
+
plan,
|
|
178
|
+
...(plan.resourceRef ? { resourceRef: plan.resourceRef } : {}),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const outcome = await command.execute(context);
|
|
183
|
+
return buildReceipt({
|
|
184
|
+
command,
|
|
185
|
+
status: outcome.status ?? "ok",
|
|
186
|
+
idempotencyKey,
|
|
187
|
+
correlationId,
|
|
188
|
+
identity,
|
|
189
|
+
transport,
|
|
190
|
+
plan,
|
|
191
|
+
...((outcome.resourceRef ?? plan.resourceRef) ? { resourceRef: outcome.resourceRef ?? plan.resourceRef } : {}),
|
|
192
|
+
...(outcome.validators ? { validators: outcome.validators } : {}),
|
|
193
|
+
...(outcome.problem ? { problem: outcome.problem } : {}),
|
|
194
|
+
output: outcome.output,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
throw toHonuaCommandError(error, command.id, failureContext);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Refuse a caller-supplied credential or authority header.
|
|
204
|
+
*
|
|
205
|
+
* Exported so a transport can screen its own header pass-through before it
|
|
206
|
+
* builds an invocation, but the runtime applies it unconditionally — a
|
|
207
|
+
* transport that forgets cannot weaken the guarantee.
|
|
208
|
+
*/
|
|
209
|
+
export function assertNoAuthorityOverride(commandId, headers, context = {}) {
|
|
210
|
+
const offending = headerNames(headers).filter((name) => RESERVED_HEADER_SET.has(name));
|
|
211
|
+
if (offending.length === 0)
|
|
212
|
+
return;
|
|
213
|
+
const named = offending.sort().join(", ");
|
|
214
|
+
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);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Refuse a caller-supplied value for a header the runtime derives.
|
|
218
|
+
*
|
|
219
|
+
* Classified `validation` rather than `authorization`: this is caller misuse of
|
|
220
|
+
* the invocation contract, not an attempt to assert authority. Use
|
|
221
|
+
* {@link HonuaCommandInvocation.idempotencyKey} / `.ifMatch` instead, so the
|
|
222
|
+
* value that travels is the value the receipt records.
|
|
223
|
+
*/
|
|
224
|
+
export function assertNoCommandKeyOverride(commandId, headers, context = {}) {
|
|
225
|
+
const offending = headerNames(headers).filter((name) => OWNED_HEADER_SET.has(name));
|
|
226
|
+
if (offending.length === 0)
|
|
227
|
+
return;
|
|
228
|
+
const named = offending.sort().join(", ");
|
|
229
|
+
throw new HonuaCommandError("validation", commandId, `Command ${commandId} refused caller-supplied header(s) it derives itself: ${named}. Set them through the invocation's \`idempotencyKey\` / \`ifMatch\` so the value on the wire is the value the receipt records.`, {
|
|
230
|
+
...context,
|
|
231
|
+
issues: offending.sort().map((name) => ({
|
|
232
|
+
path: `headers.${name}`,
|
|
233
|
+
message: "is derived by the command runtime and cannot be set by a caller",
|
|
234
|
+
})),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Strip the resource link from a plan.
|
|
239
|
+
*
|
|
240
|
+
* A plan describes a request that has *not been issued* — and on a dry run it
|
|
241
|
+
* is the whole receipt, because `execute` is never reached. A link on it would
|
|
242
|
+
* therefore be a URL for a resource that may not exist, printed by the CLI and
|
|
243
|
+
* Studio next to the word "dry run", which is precisely honua-sdk-js#1426's
|
|
244
|
+
* "preview is never reported as persisted or published" failing. `plan` is
|
|
245
|
+
* already documented to carry no request body for a related reason; carrying no
|
|
246
|
+
* resource link is the same rule applied to the response side.
|
|
247
|
+
*
|
|
248
|
+
* The executed path is unaffected: a real invocation reports the `href` the
|
|
249
|
+
* *server* returned, on `HonuaCommandOutcome.resourceRef`. What a command can
|
|
250
|
+
* no longer do is predict one.
|
|
251
|
+
*/
|
|
252
|
+
function withoutResourceLink(plan) {
|
|
253
|
+
const ref = plan.resourceRef;
|
|
254
|
+
if (!ref || ref.href === undefined)
|
|
255
|
+
return plan;
|
|
256
|
+
const { href: _href, ...rest } = ref;
|
|
257
|
+
return { ...plan, resourceRef: rest };
|
|
258
|
+
}
|
|
259
|
+
function headerNames(headers) {
|
|
260
|
+
if (!headers)
|
|
261
|
+
return [];
|
|
262
|
+
if (headers instanceof Headers)
|
|
263
|
+
return [...headers.keys()].map((name) => name.toLowerCase());
|
|
264
|
+
if (Array.isArray(headers))
|
|
265
|
+
return headers.map(([name]) => String(name).toLowerCase());
|
|
266
|
+
return Object.keys(headers).map((name) => name.toLowerCase());
|
|
267
|
+
}
|
|
268
|
+
function assertTransport(commandId, transport) {
|
|
269
|
+
if (HONUA_COMMAND_TRANSPORTS.includes(transport))
|
|
270
|
+
return transport;
|
|
271
|
+
throw new HonuaCommandError("validation", commandId, `Unknown command transport "${String(transport)}"; expected one of ${HONUA_COMMAND_TRANSPORTS.join(", ")}.`);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Withhold credential material from the plan's human-readable summary.
|
|
275
|
+
*
|
|
276
|
+
* {@link HonuaCommandPlan} promises it "carries no request body: command inputs
|
|
277
|
+
* can contain connection credentials, and a plan is rendered to terminals and
|
|
278
|
+
* logs". Omitting the body is not on its own enough: a command builds its
|
|
279
|
+
* `summary` from its own input, and an import's `sourceUrl` is routinely a
|
|
280
|
+
* presigned URL whose query string *is* the credential. Interpolated into a
|
|
281
|
+
* receipt — which `serializeHonuaCommandReceipt` exists to persist, and which
|
|
282
|
+
* `honua map publish --json` prints verbatim — that URL outlives the request it
|
|
283
|
+
* authorized, in a log the caller believes is safe to keep.
|
|
284
|
+
*
|
|
285
|
+
* So the summary goes through the SDK's one credential recognizer
|
|
286
|
+
* (`src/core/credential-redaction.ts`) on the way onto the receipt. The
|
|
287
|
+
* substitution is deterministic, so two equivalent calls still agree on
|
|
288
|
+
* `auditKey`. `path` is deliberately left alone: it is documented as the
|
|
289
|
+
* request exactly as issued, is built only from percent-encoded resource ids,
|
|
290
|
+
* and redacting an id would misreport the request. `output` is left alone too
|
|
291
|
+
* — it is the command's return value, not a rendered preview.
|
|
292
|
+
*/
|
|
293
|
+
function redactPlanSummary(plan) {
|
|
294
|
+
const summary = redactHonuaExportText(plan.summary);
|
|
295
|
+
return summary === plan.summary ? plan : { ...plan, summary };
|
|
296
|
+
}
|
|
297
|
+
function buildReceipt(parts) {
|
|
298
|
+
const projection = {
|
|
299
|
+
kind: HONUA_COMMAND_RECEIPT_KIND,
|
|
300
|
+
commandId: parts.command.id,
|
|
301
|
+
mode: parts.command.mode,
|
|
302
|
+
status: parts.status,
|
|
303
|
+
idempotencyKey: parts.idempotencyKey,
|
|
304
|
+
correlationId: parts.correlationId,
|
|
305
|
+
identity: parts.identity,
|
|
306
|
+
authorization: "server-enforced",
|
|
307
|
+
plan: redactPlanSummary(parts.plan),
|
|
308
|
+
...(parts.resourceRef ? { resourceRef: parts.resourceRef } : {}),
|
|
309
|
+
...(parts.validators ? { validators: parts.validators } : {}),
|
|
310
|
+
...(parts.problem ? { problem: parts.problem } : {}),
|
|
311
|
+
...(parts.output !== undefined ? { output: parts.output } : {}),
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
...projection,
|
|
315
|
+
transport: parts.transport,
|
|
316
|
+
auditKey: commandDigest(canonicalCommandJson(projection)),
|
|
317
|
+
};
|
|
318
|
+
}
|