@honua/app-platform 0.1.4-beta.0 → 0.1.9-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/app/index.d.ts.map +1 -1
  2. package/app/index.js +4 -0
  3. package/contract/types.d.ts +1 -1
  4. package/contract/types.d.ts.map +1 -1
  5. package/contract/types.js +4 -0
  6. package/control-plane/admin-client.d.ts +87 -0
  7. package/control-plane/admin-client.d.ts.map +1 -0
  8. package/control-plane/admin-client.js +169 -0
  9. package/control-plane/client.d.ts.map +1 -1
  10. package/control-plane/client.js +21 -3
  11. package/control-plane/commands/catalog.d.ts +133 -0
  12. package/control-plane/commands/catalog.d.ts.map +1 -0
  13. package/control-plane/commands/catalog.js +353 -0
  14. package/control-plane/commands/errors.d.ts +96 -0
  15. package/control-plane/commands/errors.d.ts.map +1 -0
  16. package/control-plane/commands/errors.js +130 -0
  17. package/control-plane/commands/identity.d.ts +71 -0
  18. package/control-plane/commands/identity.d.ts.map +1 -0
  19. package/control-plane/commands/identity.js +0 -0
  20. package/control-plane/commands/index.d.ts +39 -0
  21. package/control-plane/commands/index.d.ts.map +1 -0
  22. package/control-plane/commands/index.js +34 -0
  23. package/control-plane/commands/runtime.d.ts +146 -0
  24. package/control-plane/commands/runtime.d.ts.map +1 -0
  25. package/control-plane/commands/runtime.js +318 -0
  26. package/control-plane/commands/types.d.ts +226 -0
  27. package/control-plane/commands/types.d.ts.map +1 -0
  28. package/control-plane/commands/types.js +30 -0
  29. package/control-plane/commands/validate.d.ts +27 -0
  30. package/control-plane/commands/validate.d.ts.map +1 -0
  31. package/control-plane/commands/validate.js +132 -0
  32. package/control-plane/generated/admin-api.d.ts +30263 -0
  33. package/control-plane/generated/admin-api.d.ts.map +1 -0
  34. package/control-plane/generated/admin-api.js +5 -0
  35. package/control-plane/generated/admin-operations.d.ts +4053 -0
  36. package/control-plane/generated/admin-operations.d.ts.map +1 -0
  37. package/control-plane/generated/admin-operations.js +820 -0
  38. package/control-plane/index.d.ts +11 -0
  39. package/control-plane/index.d.ts.map +1 -1
  40. package/control-plane/index.js +8 -0
  41. package/control-plane/types.d.ts +14 -0
  42. package/control-plane/types.d.ts.map +1 -1
  43. package/controls/catalog.d.ts.map +1 -1
  44. package/controls/catalog.js +12 -1
  45. package/controls/qualification.d.ts.map +1 -1
  46. package/controls/qualification.js +61 -2
  47. package/core/client.d.ts +6 -0
  48. package/core/client.d.ts.map +1 -1
  49. package/core/client.js +12 -10
  50. package/core/credential-redaction.d.ts +163 -0
  51. package/core/credential-redaction.d.ts.map +1 -0
  52. package/core/credential-redaction.js +389 -0
  53. package/core/error-classifications.d.ts +32 -0
  54. package/core/error-classifications.d.ts.map +1 -1
  55. package/core/error-classifications.js +32 -0
  56. package/core/error-code-registry.d.ts +33 -1
  57. package/core/error-code-registry.d.ts.map +1 -1
  58. package/core/error-code-registry.js +32 -0
  59. package/core/errors.d.ts +1 -1
  60. package/core/errors.d.ts.map +1 -1
  61. package/core/geoparquet-sql.d.ts +1 -1
  62. package/core/geoparquet-sql.js +2 -2
  63. package/core/grpc-adapter.d.ts +2 -2
  64. package/core/grpc-adapter.d.ts.map +1 -1
  65. package/core/grpc-adapter.js +30 -6
  66. package/core/job-run-errors.d.ts +10 -0
  67. package/core/job-run-errors.d.ts.map +1 -0
  68. package/core/job-run-errors.js +14 -0
  69. package/core/job-run-lifecycle.d.ts +34 -0
  70. package/core/job-run-lifecycle.d.ts.map +1 -0
  71. package/core/job-run-lifecycle.js +193 -0
  72. package/core/ogc-processes.d.ts +38 -31
  73. package/core/ogc-processes.d.ts.map +1 -1
  74. package/core/ogc-processes.js +123 -173
  75. package/core/process-runner.js +15 -100
  76. package/core/stac.d.ts +2 -1
  77. package/core/stac.d.ts.map +1 -1
  78. package/core/stac.js +29 -11
  79. package/core/surfaces.d.ts +2 -8
  80. package/core/surfaces.d.ts.map +1 -1
  81. package/core/surfaces.js +21 -112
  82. package/core/types.d.ts +22 -8
  83. package/core/types.d.ts.map +1 -1
  84. package/gen/geospatial/v1/common_pb.d.ts +514 -0
  85. package/gen/geospatial/v1/common_pb.d.ts.map +1 -0
  86. package/gen/geospatial/v1/common_pb.js +335 -0
  87. package/gen/geospatial/v1/execution_types_pb.d.ts +1186 -0
  88. package/gen/geospatial/v1/execution_types_pb.d.ts.map +1 -0
  89. package/gen/geospatial/v1/execution_types_pb.js +396 -0
  90. package/gen/geospatial/v1/feature_service_pb.d.ts +403 -0
  91. package/gen/geospatial/v1/feature_service_pb.d.ts.map +1 -0
  92. package/gen/geospatial/v1/feature_service_pb.js +47 -0
  93. package/gen/geospatial/v1/index.d.ts +5 -0
  94. package/gen/geospatial/v1/index.d.ts.map +1 -0
  95. package/gen/geospatial/v1/index.js +9 -0
  96. package/gen/geospatial/v1/spatial_types_pb.d.ts +285 -0
  97. package/gen/geospatial/v1/spatial_types_pb.d.ts.map +1 -0
  98. package/gen/geospatial/v1/spatial_types_pb.js +58 -0
  99. package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts +379 -0
  100. package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts.map +1 -0
  101. package/gen/geospatial/v1/workspace_artifact_types_pb.js +151 -0
  102. package/generated-app/projection.d.ts.map +1 -1
  103. package/generated-app/projection.js +10 -1
  104. package/interactions/declarative-contract.d.ts +149 -0
  105. package/interactions/declarative-contract.d.ts.map +1 -0
  106. package/interactions/declarative-contract.js +293 -0
  107. package/interactions/declarative.d.ts +198 -0
  108. package/interactions/declarative.d.ts.map +1 -0
  109. package/interactions/declarative.js +470 -0
  110. package/interactions/feature-state.d.ts +7 -0
  111. package/interactions/feature-state.d.ts.map +1 -1
  112. package/interactions/feature-state.js +10 -2
  113. package/map/maplibre-target.js +1 -1
  114. package/operate/client.d.ts +11 -1
  115. package/operate/client.d.ts.map +1 -1
  116. package/operate/client.js +39 -2
  117. package/operate/index.d.ts +1 -1
  118. package/operate/index.d.ts.map +1 -1
  119. package/operate/types.d.ts +40 -6
  120. package/operate/types.d.ts.map +1 -1
  121. package/package.json +2 -2
  122. package/query-planner/grpc.d.ts +4 -4
  123. package/query-planner/grpc.d.ts.map +1 -1
  124. package/query-planner/grpc.js +11 -11
  125. package/query-planner/types.d.ts +4 -4
  126. package/query-planner/types.d.ts.map +1 -1
  127. package/realtime/sse.d.ts.map +1 -1
  128. package/realtime/sse.js +7 -1
  129. package/runtime/errors.d.ts +1 -1
  130. package/runtime/errors.d.ts.map +1 -1
  131. package/runtime/errors.js +2 -0
  132. package/runtime/generated/map-package-schema-meta.d.ts +11 -0
  133. package/runtime/generated/map-package-schema-meta.d.ts.map +1 -0
  134. package/runtime/generated/map-package-schema-meta.js +12 -0
  135. package/runtime/generated/map-package-schema-validator.d.ts +16 -0
  136. package/runtime/generated/map-package-schema-validator.d.ts.map +1 -0
  137. package/runtime/generated/map-package-schema-validator.js +1422 -0
  138. package/runtime/index.d.ts +5 -1
  139. package/runtime/index.d.ts.map +1 -1
  140. package/runtime/index.js +6 -0
  141. package/runtime/map-package-export.d.ts +151 -0
  142. package/runtime/map-package-export.d.ts.map +1 -0
  143. package/runtime/map-package-export.js +636 -0
  144. package/runtime/map-package-validation.d.ts +21 -1
  145. package/runtime/map-package-validation.d.ts.map +1 -1
  146. package/runtime/map-package-validation.js +132 -95
  147. package/runtime/map-package.d.ts +80 -0
  148. package/runtime/map-package.d.ts.map +1 -1
  149. package/runtime/style-compose.d.ts.map +1 -1
  150. package/runtime/style-compose.js +78 -9
  151. package/scene-workspace/cesium-adapter.d.ts +8 -8
  152. package/scene-workspace/cesium-adapter.d.ts.map +1 -1
  153. package/scene-workspace/cesium-adapter.js +31 -18
  154. package/scene-workspace/primitives.d.ts.map +1 -1
  155. package/scene-workspace/primitives.js +9 -6
  156. package/scene-workspace/source-to-cesium.js +1 -1
  157. package/studio/chart-spec.d.ts +6 -6
  158. package/studio/chart-spec.d.ts.map +1 -1
  159. package/studio/chart-spec.js +6 -6
  160. package/studio/index.d.ts +17 -7
  161. package/studio/index.d.ts.map +1 -1
  162. package/studio/index.js +15 -5
  163. package/studio/lifecycle-client.d.ts +262 -22
  164. package/studio/lifecycle-client.d.ts.map +1 -1
  165. package/studio/lifecycle-client.js +524 -24
  166. package/studio/lifecycle-errors.d.ts +8 -2
  167. package/studio/lifecycle-errors.d.ts.map +1 -1
  168. package/studio/lifecycle-errors.js +6 -1
  169. package/studio/lifecycle-types.d.ts +228 -8
  170. package/studio/lifecycle-types.d.ts.map +1 -1
  171. package/studio/publication-status.d.ts +55 -0
  172. package/studio/publication-status.d.ts.map +1 -0
  173. package/studio/publication-status.js +103 -0
  174. package/web-components/application-context.d.ts +186 -0
  175. package/web-components/application-context.d.ts.map +1 -0
  176. package/web-components/application-context.js +429 -0
  177. package/web-components/elements.d.ts +10 -1
  178. package/web-components/elements.d.ts.map +1 -1
  179. package/web-components/elements.js +187 -7
  180. package/web-components/export-redaction.d.ts +11 -102
  181. package/web-components/export-redaction.d.ts.map +1 -1
  182. package/web-components/export-redaction.js +7 -260
  183. package/web-components/feature-editor-offline.d.ts +32 -0
  184. package/web-components/feature-editor-offline.d.ts.map +1 -0
  185. package/web-components/feature-editor-offline.js +115 -0
  186. package/web-components/feature-editor-workflow.d.ts +85 -22
  187. package/web-components/feature-editor-workflow.d.ts.map +1 -1
  188. package/web-components/feature-editor-workflow.js +240 -0
  189. package/web-components/feature-editor.d.ts +20 -6
  190. package/web-components/feature-editor.d.ts.map +1 -1
  191. package/web-components/feature-editor.js +159 -7
  192. package/web-components/feature-inspection.d.ts +287 -0
  193. package/web-components/feature-inspection.d.ts.map +1 -0
  194. package/web-components/feature-inspection.js +1464 -0
  195. package/web-components/feature-mutation.d.ts +77 -0
  196. package/web-components/feature-mutation.d.ts.map +1 -0
  197. package/web-components/feature-mutation.js +137 -0
  198. package/web-components/feature-table-engine.d.ts +94 -4
  199. package/web-components/feature-table-engine.d.ts.map +1 -1
  200. package/web-components/feature-table-engine.js +365 -18
  201. package/web-components/feature-table-view.d.ts +35 -1
  202. package/web-components/feature-table-view.d.ts.map +1 -1
  203. package/web-components/feature-table-view.js +128 -12
  204. package/web-components/index.d.ts +14 -6
  205. package/web-components/index.d.ts.map +1 -1
  206. package/web-components/index.js +8 -2
  207. package/gen/honua/v1/feature_service_pb.d.ts +0 -918
  208. package/gen/honua/v1/feature_service_pb.d.ts.map +0 -1
  209. package/gen/honua/v1/feature_service_pb.js +0 -375
@@ -39,239 +39,17 @@
39
39
  *
40
40
  * @module
41
41
  */
42
- import { HonuaSdkError } from "../core/error-envelope.js";
42
+ import { assertCredentialFreeExportText, containsCredentialMaterial, projectExportEndpoint, redactHonuaExportText, } from "../core/credential-redaction.js";
43
43
  /** Schema id stamped on every sanitized state document. */
44
44
  export const HONUA_EXPORT_STATE_SCHEMA = "honua.app-platform.export-state.v1";
45
- /** Placeholder substituted for any value the export pipeline refuses to emit. */
46
- export const HONUA_EXPORT_REDACTED = "[REDACTED]";
47
- /** Thrown when the export pipeline cannot produce a provably credential-free artifact. */
48
- export class HonuaExportSafetyError extends HonuaSdkError {
49
- reason;
50
- constructor(message, reason = "assertion") {
51
- super("app.export-unsafe", message);
52
- this.name = "HonuaExportSafetyError";
53
- this.reason = reason;
54
- }
55
- }
56
- // ── credential recognition ───────────────────────────────────────────────
57
- /**
58
- * Property names that are never exported, whatever their value. Matched
59
- * case-insensitively against the whole key (substring match), so `apiKey`,
60
- * `X-API-KEY`, `refresh_token`, and `sessionCookie` are all caught.
61
- */
62
- const SENSITIVE_KEY = /(?:api[-_]?key|auth|bearer|cookie|credential|cred(?:s)?\b|jwt|nonce|pass(?:word|wd)?|private[-_]?key|pwd|secret|session|sig(?:nature)?|subscription[-_]?key|token)/i;
63
- /** Property names that leak *authorization scope* rather than the secret itself. */
64
- const AUTHORIZATION_SCOPE_KEY = /(?:^|[-_.])(?:scope|scopes|audience|aud|tenant|tenant[-_]?id|client[-_]?id|grant|grant[-_]?type|roles|permissions|principal|subject|assume[-_]?role)(?:$|[-_.])/i;
65
- /** Query-parameter names that make a URL a *signed* URL (presigned S3/GCS/SAS/Esri/CDN). */
66
- const SIGNED_QUERY_NAME = /(?:^|[-_])(?:sig|signature|token|access[-_]?token|id[-_]?token|refresh[-_]?token|api[-_]?key|apikey|key|subscription[-_]?key|code|password|pwd|auth|jwt|hmac|policy|credential|se|sp|sv|st|srt|ss|skoid|sktid|sig)(?:$|[-_])/i;
67
- /**
68
- * Free-text credential shapes. Every pattern is a linear scan over bounded
69
- * character classes — no nested quantifiers, no backtracking traps (the repo's
70
- * CodeQL ReDoS rule).
71
- */
72
- const CREDENTIAL_PATTERNS = [
73
- [/\b(?:Bearer|Basic|Digest|Negotiate)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "[REDACTED_AUTH]"],
74
- // `scheme://user:password@host` — basic-auth credentials embedded in a URL.
75
- // `projectExportEndpoint` refuses these structurally, but a URL can also reach
76
- // an export through an adapter's free-text error message, so the text scan has
77
- // to recognize the same shape.
78
- [/\b[a-z][a-z0-9+.-]{1,31}:\/\/[^\s/@:]{1,128}:[^\s/@]{1,128}@/gi, "[REDACTED_URL_CREDENTIALS]@"],
79
- [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, "[REDACTED_JWT]"],
80
- [/\bAKIA[0-9A-Z]{16}\b/g, "[REDACTED_AWS_KEY]"],
81
- [/\bASIA[0-9A-Z]{16}\b/g, "[REDACTED_AWS_KEY]"],
82
- [/\bAIza[0-9A-Za-z_-]{20,}/g, "[REDACTED_PROVIDER_TOKEN]"],
83
- [/\bgh[pousr]_[A-Za-z0-9]{16,}/g, "[REDACTED_PROVIDER_TOKEN]"],
84
- [/\bglpat-[A-Za-z0-9_-]{10,}/g, "[REDACTED_PROVIDER_TOKEN]"],
85
- [/\bxox[baprs]-[A-Za-z0-9-]{8,}/gi, "[REDACTED_PROVIDER_TOKEN]"],
86
- [/\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{8,}/gi, "[REDACTED_PROVIDER_TOKEN]"],
87
- [/\bsk-[A-Za-z0-9]{16,}/g, "[REDACTED_PROVIDER_TOKEN]"],
88
- // Labelled `name=value` / `name: value` pairs. The name alternation covers
89
- // the short, provider-specific parameter names real signed URLs use — Azure
90
- // SAS `sig`/`sv`/`se`/`st`, AWS/GCS `x-amz-signature`/`x-goog-signature`, and
91
- // the bare `key` that tile providers favour — because those values are
92
- // frequently *short* (a 24-character SAS signature is common) and so slip
93
- // under any entropy heuristic. `\b` before each name keeps `turnkey=` and
94
- // `monkey=` from matching.
95
- [
96
- /\b(access[-_]?key|api[-_]?key|apikey|authorization|client[-_]?secret|cookie|credential|jwt|key|pass(?:password)?|password|pwd|se|secret|session|sig|signature|st|subscription[-_]?key|sv|token|x-amz-[a-z-]{1,32}|x-goog-[a-z-]{1,32})\s*[:=]\s*(?:"[^"]{0,512}"|'[^']{0,512}'|[^\s&,;}"']{1,512})/gi,
97
- `$1=${HONUA_EXPORT_REDACTED}`,
98
- ],
99
- ];
100
- /**
101
- * High-entropy blob heuristic, applied last so labelled matches win first.
102
- *
103
- * Deliberately excludes `/` and `-` from the character class. Including them
104
- * made ordinary URL paths match — `arcgis/rest/services/Roads/FeatureServer/0`
105
- * is 41 characters of `[A-Za-z0-9/]` and was being reported as a secret, which
106
- * would have thrown away the endpoint an attribution block legitimately needs.
107
- * Base64 blobs containing `/` still get caught: in practice they appear as a
108
- * labelled parameter value or an `Authorization` header, both of which the
109
- * patterns above match first.
110
- */
111
- const OPAQUE_SECRET = /\b[A-Za-z0-9_+=]{40,}\b/g;
112
- function decodePercentEncoding(value) {
113
- let decoded = value;
114
- for (let pass = 0; pass < 2; pass += 1) {
115
- if (!decoded.includes("%"))
116
- break;
117
- let next;
118
- try {
119
- next = decodeURIComponent(decoded);
120
- }
121
- catch {
122
- break;
123
- }
124
- if (next === decoded)
125
- break;
126
- decoded = next;
127
- }
128
- return decoded;
129
- }
130
- /**
131
- * Redacts credential material from arbitrary human-readable text: adapter
132
- * error messages, titles, status strings, log lines, event detail messages.
133
- *
134
- * Percent-encoded payloads are decoded (twice, bounded) before matching so a
135
- * `%42earer%20abc` style evasion cannot slip a token past the scan; the
136
- * returned string is the redacted *decoded* form when decoding changed
137
- * anything, because emitting the still-encoded original would defeat the
138
- * point.
139
- */
140
- export function redactHonuaExportText(value) {
141
- if (value.length === 0)
142
- return value;
143
- let redacted = decodePercentEncoding(value);
144
- for (const [pattern, replacement] of CREDENTIAL_PATTERNS) {
145
- redacted = redacted.replace(pattern, replacement);
146
- }
147
- return redacted.replace(OPAQUE_SECRET, "[REDACTED_TOKEN]");
148
- }
149
- /** Whether `value` still looks like it carries credential material. */
150
- export function containsCredentialMaterial(value) {
151
- return redactHonuaExportText(value) !== decodePercentEncoding(value);
152
- }
153
- /**
154
- * Fail-closed assertion for finished export payloads (REQ-002). Throws
155
- * {@link HonuaExportSafetyError} rather than scrubbing, because reaching this
156
- * point with credential material means an earlier layer is broken.
157
- */
158
- export function assertCredentialFreeExportText(value, label) {
159
- if (containsCredentialMaterial(value)) {
160
- throw new HonuaExportSafetyError(`${label} still contains credential material after redaction; refusing to emit the export.`, "assertion");
161
- }
162
- }
163
- // ── binary artifact scanning ─────────────────────────────────────────────
164
- /** Shortest printable run worth scanning. Below this it is structural noise, not text. */
165
- const MIN_PRINTABLE_RUN = 4;
166
- /** Printable text accumulated before a chunk is scanned and flushed. */
167
- const PRINTABLE_CHUNK_BYTES = 1 << 20;
168
45
  /**
169
- * Characters carried between chunks so a token straddling a flush boundary is
170
- * still matched. Comfortably longer than the longest pattern this module has.
46
+ * The credential recognizer itself now lives in `src/core/credential-redaction.ts`
47
+ * so `src/runtime`'s portable MapPackage export (#1426) can share exactly one
48
+ * scanner across the `@honua/sdk` / `@honua/app-platform` split-package
49
+ * boundary. Every symbol is re-exported here unchanged: this module's published
50
+ * surface, and its two-layer guarantee, are identical to before the move.
171
51
  */
172
- const PRINTABLE_CHUNK_OVERLAP = 512;
173
- /**
174
- * Extracts runs of printable ASCII from a binary buffer, joined by newlines.
175
- *
176
- * This is the `strings(1)` idea, and it exists because "binary" is not a
177
- * security boundary. A PNG `tEXt`/`iTXt` chunk, a JPEG `COM` marker, PDF
178
- * metadata or an embedded XMP packet, an EXIF `UserComment`, a ZIP comment —
179
- * all carry plainly readable text inside a buffer that will never decode as
180
- * UTF-8 as a whole. Scanning only whole-buffer-decodable artifacts would let a
181
- * token ride out inside any of them.
182
- *
183
- * Runs shorter than {@link MIN_PRINTABLE_RUN} are dropped: they are the
184
- * incidental byte values that fall in the ASCII range inside compressed data,
185
- * and keeping them would produce noise without ever forming a credential.
186
- */
187
- export function extractPrintableRuns(bytes, start = 0, end = bytes.byteLength) {
188
- const runs = [];
189
- let current = "";
190
- for (let index = start; index < end; index += 1) {
191
- const byte = bytes[index];
192
- // Printable ASCII plus tab. Deliberately not the full latin-1 upper range:
193
- // every credential shape this module recognizes is ASCII, and admitting
194
- // 0x80-0xFF would turn arbitrary compressed data into "text".
195
- if ((byte >= 0x20 && byte <= 0x7e) || byte === 0x09) {
196
- current += String.fromCharCode(byte);
197
- continue;
198
- }
199
- if (current.length >= MIN_PRINTABLE_RUN)
200
- runs.push(current);
201
- current = "";
202
- }
203
- if (current.length >= MIN_PRINTABLE_RUN)
204
- runs.push(current);
205
- return runs.join("\n");
206
- }
207
- /**
208
- * Fail-closed credential scan over a binary artifact (REQ-002).
209
- *
210
- * Scans in bounded chunks with an overlap, so an arbitrarily large artifact
211
- * costs bounded memory while a token that straddles a chunk boundary is still
212
- * caught. Throws {@link HonuaExportSafetyError} on a hit, like the text scan —
213
- * a credential surviving to this point means an earlier layer is broken, and
214
- * emitting the bytes anyway is the one outcome that cannot be walked back.
215
- */
216
- export function assertCredentialFreeExportBytes(bytes, label) {
217
- let carry = "";
218
- for (let offset = 0; offset < bytes.byteLength; offset += PRINTABLE_CHUNK_BYTES) {
219
- const end = Math.min(bytes.byteLength, offset + PRINTABLE_CHUNK_BYTES);
220
- const printable = extractPrintableRuns(bytes, offset, end);
221
- if (printable.length === 0 && carry.length === 0)
222
- continue;
223
- assertCredentialFreeExportText(`${carry}${printable}`, label);
224
- carry = printable.slice(-PRINTABLE_CHUNK_OVERLAP);
225
- }
226
- }
227
- /**
228
- * Reduces a source URL to `origin + pathname`, always dropping the query
229
- * string and fragment.
230
- *
231
- * Query strings are where every practical map-service credential lives (Esri
232
- * `?token=`, Azure SAS `?sv=...&sig=`, S3/GCS presigned `?X-Amz-Signature=`,
233
- * tile-provider `?api_key=`), and a partial per-parameter allowlist has to be
234
- * right about every provider forever. Dropping the whole query is the only
235
- * rule that is right by construction; the origin+path that remains is what an
236
- * attribution/provenance block legitimately needs.
237
- *
238
- * Non-HTTP(S) schemes (`data:`, `blob:`, `file:`), URLs with embedded
239
- * `user:password@` userinfo, and unparseable strings yield no endpoint at all.
240
- */
241
- export function projectExportEndpoint(url) {
242
- const trimmed = url.trim();
243
- if (trimmed.length === 0 || trimmed.length > 4096) {
244
- return { endpoint: undefined, reason: "unsupported-value" };
245
- }
246
- let parsed;
247
- try {
248
- parsed = new URL(trimmed);
249
- }
250
- catch {
251
- // Relative locators carry no host and therefore no credential context, but
252
- // they can still carry a query string; keep only the path portion.
253
- const [path] = trimmed.split(/[?#]/, 1);
254
- if (path.length === 0 || containsCredentialMaterial(path)) {
255
- return { endpoint: undefined, reason: "credential-pattern" };
256
- }
257
- return path === trimmed ? { endpoint: path } : { endpoint: path, reason: "signed-url" };
258
- }
259
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
260
- return { endpoint: undefined, reason: "unsupported-value" };
261
- }
262
- if (parsed.username.length > 0 || parsed.password.length > 0) {
263
- return { endpoint: undefined, reason: "signed-url" };
264
- }
265
- const hadSecretQuery = [...parsed.searchParams.keys()].some((name) => SIGNED_QUERY_NAME.test(name) || SENSITIVE_KEY.test(name));
266
- const endpoint = `${parsed.origin}${parsed.pathname}`;
267
- if (containsCredentialMaterial(endpoint)) {
268
- return { endpoint: undefined, reason: "credential-pattern" };
269
- }
270
- const droppedSomething = parsed.search.length > 0 || parsed.hash.length > 0;
271
- if (hadSecretQuery)
272
- return { endpoint, reason: "signed-url" };
273
- return droppedSomething ? { endpoint, reason: "unsupported-value" } : { endpoint };
274
- }
52
+ export { HONUA_EXPORT_REDACTED, HonuaExportSafetyError, assertCredentialFreeExportBytes, assertCredentialFreeExportText, containsCredentialMaterial, extractPrintableRuns, isSensitiveExportKey, projectExportEndpoint, redactHonuaExportText, sanitizeHonuaExportHeaders, } from "../core/credential-redaction.js";
275
53
  // ── filenames ────────────────────────────────────────────────────────────
276
54
  const FILENAME_FALLBACK = "honua-export";
277
55
  const FILENAME_MAX_LENGTH = 96;
@@ -555,34 +333,3 @@ export function sanitizeHonuaExportState(state, options = {}) {
555
333
  * export (a server-side renderer, a tile fetch for a print layout). Everything
556
334
  * outside this set is a private header for export purposes (REQ-002).
557
335
  */
558
- const EXPORTABLE_HEADERS = new Set(["accept", "accept-language", "content-type", "user-agent"]);
559
- /**
560
- * Filters a header map down to the export allowlist, recording every drop.
561
- * Exposed so adapters can prove — in their own tests — that no `Authorization`,
562
- * `Cookie`, or provider-specific key header rides along with export metadata.
563
- */
564
- export function sanitizeHonuaExportHeaders(headers) {
565
- const redactions = [];
566
- const output = {};
567
- for (const [rawName, rawValue] of Object.entries(headers ?? {})) {
568
- const name = rawName.toLowerCase();
569
- if (!EXPORTABLE_HEADERS.has(name)) {
570
- redactions.push({
571
- path: `headers[${name}]`,
572
- reason: SENSITIVE_KEY.test(name) || AUTHORIZATION_SCOPE_KEY.test(name) ? "sensitive-key" : "private-header",
573
- });
574
- continue;
575
- }
576
- const value = redactHonuaExportText(rawValue);
577
- if (value !== rawValue) {
578
- redactions.push({ path: `headers[${name}]`, reason: "credential-pattern" });
579
- continue;
580
- }
581
- output[name] = value.slice(0, 512);
582
- }
583
- return { headers: output, redactions };
584
- }
585
- /** Whether a property name is refused outright by the export pipeline. */
586
- export function isSensitiveExportKey(name) {
587
- return SENSITIVE_KEY.test(name) || AUTHORIZATION_SCOPE_KEY.test(name);
588
- }
@@ -0,0 +1,32 @@
1
+ import type { OfflineEditEnqueueResult, OfflineEditQueue, OfflineEditQueuePartition, OfflineQueuedEdit } from "../offline/edit-queue.js";
2
+ import type { HonuaFeatureEditorConflictChoice, HonuaFeatureEditorOfflineState, HonuaFeatureEditorSnapshot, HonuaFeatureEditorWorkflow } from "./feature-editor-workflow.js";
3
+ export type HonuaFeatureEditorOfflineErrorCode = "invalid-draft" | "attachment-payload" | "invalid-value";
4
+ /** A draft cannot be persisted without an explicit, lossless queue mapping. */
5
+ export declare class HonuaFeatureEditorOfflineError extends Error {
6
+ readonly code: HonuaFeatureEditorOfflineErrorCode;
7
+ readonly name = "HonuaFeatureEditorOfflineError";
8
+ constructor(code: HonuaFeatureEditorOfflineErrorCode, message: string);
9
+ }
10
+ export interface QueueHonuaFeatureEditorDraftOptions {
11
+ /** Precomputed authorization partition digest. Raw credentials are never accepted. */
12
+ readonly authorizationScopeDigest: `sha256:${string}`;
13
+ readonly idempotencyKey: string;
14
+ readonly dependencyIds?: readonly `sha256:${string}`[];
15
+ }
16
+ export interface QueueHonuaFeatureEditorDraftResult<T = Record<string, unknown>> {
17
+ readonly enqueue: OfflineEditEnqueueResult;
18
+ readonly snapshot: HonuaFeatureEditorSnapshot<T>;
19
+ }
20
+ /**
21
+ * Persist the current validated draft in the canonical offline edit queue.
22
+ * Attachment payloads fail closed because the queue contract cannot represent
23
+ * them; silently dropping a staged upload would make the recovered edit lie.
24
+ */
25
+ export declare function queueHonuaFeatureEditorDraft<T>(workflow: HonuaFeatureEditorWorkflow<T>, queue: OfflineEditQueue, options: QueueHonuaFeatureEditorDraftOptions): Promise<QueueHonuaFeatureEditorDraftResult<T>>;
26
+ /** Project a later replay/reconnect transition back into the same workflow. */
27
+ export declare function reconcileHonuaFeatureEditorOfflineEdit<T>(workflow: HonuaFeatureEditorWorkflow<T>, edit: OfflineQueuedEdit): HonuaFeatureEditorSnapshot<T>;
28
+ /** Resolve a queued conflict durably before updating the component state. */
29
+ export declare function resolveHonuaFeatureEditorOfflineConflict<T>(workflow: HonuaFeatureEditorWorkflow<T>, queue: OfflineEditQueue, edit: OfflineQueuedEdit, choice: HonuaFeatureEditorConflictChoice, partition: OfflineEditQueuePartition): Promise<HonuaFeatureEditorSnapshot<T>>;
30
+ /** Convert the durable queue record to the payload-free component projection. */
31
+ export declare function offlineStateFromQueuedEdit(edit: OfflineQueuedEdit): HonuaFeatureEditorOfflineState;
32
+ //# sourceMappingURL=feature-editor-offline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-editor-offline.d.ts","sourceRoot":"","sources":["../../../src/web-components/feature-editor-offline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EAExB,gBAAgB,EAChB,yBAAyB,EACzB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,gCAAgC,EAChC,8BAA8B,EAC9B,0BAA0B,EAC1B,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,kCAAkC,GAAG,eAAe,GAAG,oBAAoB,GAAG,eAAe,CAAC;AAE1G,+EAA+E;AAC/E,qBAAa,8BAA+B,SAAQ,KAAK;aAIrC,IAAI,EAAE,kCAAkC;IAH1D,SAAgB,IAAI,oCAAoC;gBAGtC,IAAI,EAAE,kCAAkC,EACxD,OAAO,EAAE,MAAM;CAIlB;AAED,MAAM,WAAW,mCAAmC;IAClD,sFAAsF;IACtF,QAAQ,CAAC,wBAAwB,EAAE,UAAU,MAAM,EAAE,CAAC;IACtD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,UAAU,MAAM,EAAE,EAAE,CAAC;CACxD;AAED,MAAM,WAAW,kCAAkC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7E,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAsB,4BAA4B,CAAC,CAAC,EAClD,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,EACvC,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,mCAAmC,GAC3C,OAAO,CAAC,kCAAkC,CAAC,CAAC,CAAC,CAAC,CAyChD;AAED,+EAA+E;AAC/E,wBAAgB,sCAAsC,CAAC,CAAC,EACtD,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,EACvC,IAAI,EAAE,iBAAiB,GACtB,0BAA0B,CAAC,CAAC,CAAC,CAE/B;AAED,6EAA6E;AAC7E,wBAAsB,wCAAwC,CAAC,CAAC,EAC9D,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,EACvC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,gCAAgC,EACxC,SAAS,EAAE,yBAAyB,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAUxC;AAED,iFAAiF;AACjF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,GAAG,8BAA8B,CAmBlG"}
@@ -0,0 +1,115 @@
1
+ /** A draft cannot be persisted without an explicit, lossless queue mapping. */
2
+ export class HonuaFeatureEditorOfflineError extends Error {
3
+ code;
4
+ name = "HonuaFeatureEditorOfflineError";
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.code = code;
8
+ }
9
+ }
10
+ /**
11
+ * Persist the current validated draft in the canonical offline edit queue.
12
+ * Attachment payloads fail closed because the queue contract cannot represent
13
+ * them; silently dropping a staged upload would make the recovered edit lie.
14
+ */
15
+ export async function queueHonuaFeatureEditorDraft(workflow, queue, options) {
16
+ const snapshot = workflow.snapshot();
17
+ const model = workflow.sketchModel();
18
+ if (!model || !snapshot.operation || !snapshot.form) {
19
+ throw new HonuaFeatureEditorOfflineError("invalid-draft", "Open an edit draft before adding it to the offline queue.");
20
+ }
21
+ if (!workflow.validate().valid) {
22
+ throw new HonuaFeatureEditorOfflineError("invalid-draft", "Fix validation errors before adding the edit to the offline queue.");
23
+ }
24
+ if (snapshot.attachments.length > 0) {
25
+ throw new HonuaFeatureEditorOfflineError("attachment-payload", "Offline feature edits do not persist attachment payloads. Remove the staged attachments or submit while online.");
26
+ }
27
+ const feature = model.snapshot().feature;
28
+ const attributes = jsonRecord(feature.attributes, "attributes");
29
+ const geometry = feature.geometry === undefined ? undefined : jsonValue(feature.geometry, "geometry");
30
+ const enqueue = await queue.enqueue({
31
+ authorizationScopeDigest: options.authorizationScopeDigest,
32
+ sourceId: workflow.source.descriptor.id,
33
+ idempotencyKey: requiredText(options.idempotencyKey, "idempotencyKey"),
34
+ edit: {
35
+ operation: snapshot.operation === "create" ? "add" : snapshot.operation,
36
+ ...(feature.id !== undefined ? { featureId: feature.id } : {}),
37
+ ...(Object.keys(attributes).length > 0 ? { attributes } : {}),
38
+ ...(geometry !== undefined ? { geometry } : {}),
39
+ },
40
+ ...(options.dependencyIds ? { dependencyIds: options.dependencyIds } : {}),
41
+ });
42
+ return Object.freeze({
43
+ enqueue,
44
+ snapshot: workflow.applyOfflineState(offlineStateFromQueuedEdit(enqueue.edit)),
45
+ });
46
+ }
47
+ /** Project a later replay/reconnect transition back into the same workflow. */
48
+ export function reconcileHonuaFeatureEditorOfflineEdit(workflow, edit) {
49
+ return workflow.applyOfflineState(offlineStateFromQueuedEdit(edit));
50
+ }
51
+ /** Resolve a queued conflict durably before updating the component state. */
52
+ export async function resolveHonuaFeatureEditorOfflineConflict(workflow, queue, edit, choice, partition) {
53
+ const conflictId = edit.conflict?.conflictId;
54
+ if (edit.state !== "conflicted" || !conflictId) {
55
+ throw new HonuaFeatureEditorOfflineError("invalid-draft", "The offline edit does not have a conflict to resolve.");
56
+ }
57
+ const resolved = await queue.resolveConflict(edit.id, partition, {
58
+ conflictId,
59
+ choice: choice === "keep-mine" ? "accept-client" : "accept-server",
60
+ });
61
+ return reconcileHonuaFeatureEditorOfflineEdit(workflow, resolved);
62
+ }
63
+ /** Convert the durable queue record to the payload-free component projection. */
64
+ export function offlineStateFromQueuedEdit(edit) {
65
+ const state = edit.state === "leased"
66
+ ? "pending"
67
+ : edit.state === "cancelled" && edit.conflictResolution?.disposition === "discarded"
68
+ ? "discarded"
69
+ : edit.state;
70
+ return Object.freeze({
71
+ sourceId: edit.sourceId,
72
+ queueId: edit.id,
73
+ idempotencyKey: edit.idempotencyKey,
74
+ state,
75
+ ...(edit.retry?.retryAt ? { retryAt: edit.retry.retryAt } : {}),
76
+ ...(edit.retry?.reasonCode ? { reasonCode: edit.retry.reasonCode } : {}),
77
+ ...(edit.applied?.appliedAt ? { appliedAt: edit.applied.appliedAt } : {}),
78
+ ...(edit.conflict?.conflictId ? { conflictId: edit.conflict.conflictId } : {}),
79
+ ...(edit.conflict?.serverGeneration ? { serverGeneration: edit.conflict.serverGeneration } : {}),
80
+ ...(edit.cancellation?.reasonCode ? { reasonCode: edit.cancellation.reasonCode } : {}),
81
+ });
82
+ }
83
+ function jsonRecord(value, path) {
84
+ if (!isRecord(value))
85
+ throw invalidValue(path);
86
+ const output = {};
87
+ for (const [key, item] of Object.entries(value)) {
88
+ if (item !== undefined)
89
+ output[key] = jsonValue(item, `${path}.${key}`);
90
+ }
91
+ return output;
92
+ }
93
+ function jsonValue(value, path) {
94
+ if (value === null || typeof value === "string" || typeof value === "boolean")
95
+ return value;
96
+ if (typeof value === "number" && Number.isFinite(value))
97
+ return value;
98
+ if (Array.isArray(value))
99
+ return value.map((item, index) => jsonValue(item, `${path}[${index}]`));
100
+ if (isRecord(value))
101
+ return jsonRecord(value, path);
102
+ throw invalidValue(path);
103
+ }
104
+ function invalidValue(path) {
105
+ return new HonuaFeatureEditorOfflineError("invalid-value", `${path} must contain only JSON-compatible values.`);
106
+ }
107
+ function isRecord(value) {
108
+ return typeof value === "object" && value !== null && !Array.isArray(value);
109
+ }
110
+ function requiredText(value, name) {
111
+ const normalized = value.trim();
112
+ if (normalized.length === 0)
113
+ throw new HonuaFeatureEditorOfflineError("invalid-draft", `${name} is required.`);
114
+ return normalized;
115
+ }