|
1 | 1 | # @objectstack/example-showcase |
2 | 2 |
|
| 3 | +## 0.3.14-rc.6 |
| 4 | + |
| 5 | +### Patch Changes |
| 6 | + |
| 7 | +- 284e7d2: fix(rest): a crashing hook body answers the sanitised fault envelope, not a raw `TypeError` at 400 (#7543) |
| 8 | + |
| 9 | + `POST /api/v1/data/showcase_task` with `{"title": 12345}` answered |
| 10 | + |
| 11 | + ``` |
| 12 | + 400 { "error": "TypeError: not a function", "object": "showcase_task" } |
| 13 | + ``` |
| 14 | + |
| 15 | + — a JS runtime error as the client-facing message, in a body with no `code` at |
| 16 | + all. Two contract breaks in one response: an internal fault echoed verbatim to a |
| 17 | + caller, and an error body outside the ledgered envelope, so a client keying on |
| 18 | + `code` got nothing. |
| 19 | + |
| 20 | + **The seam.** `mapDataError` has two sandbox-unwrap branches, and they are the |
| 21 | + only ones in the file that emit `{ error, object }` with no `code` at 400. They |
| 22 | + exist for one shape: a hook or action body that runs |
| 23 | + `throw new Error('删除被阻断:仍有未结清的发票')` — an author writing a business |
| 24 | + rule whose message _is_ the remedy, which is answered verbatim at 400 and |
| 25 | + deliberately without a `code`. A body that instead **crashes** arrives as a |
| 26 | + thrown error too, so it took the same branch and its `TypeError` went out as if |
| 27 | + it were that author's message. |
| 28 | + |
| 29 | + **The fix.** Both branches now separate a body that _reported_ something from a |
| 30 | + body that _faulted_, by the thrown error's constructor name — the sandbox |
| 31 | + stringifies a throw as `<name>: <message>`, so a leading `TypeError:`, |
| 32 | + `ReferenceError:`, `RangeError:`, `SyntaxError:`, `URIError:`, `EvalError:`, |
| 33 | + `InternalError:` or `AggregateError:` is structural evidence of a crash rather |
| 34 | + than a keyword heuristic over prose. A crash answers the same sanitised |
| 35 | + `500 INTERNAL_ERROR` the mapper's terminal branch already gives — which is not |
| 36 | + new policy: that branch's own contract (#5489) names this exact case ("a plain |
| 37 | + handler bug (`TypeError: x is not a function`) … server faults that a caller |
| 38 | + cannot fix and a caller SHOULD retry"). The unwraps simply sat above it and |
| 39 | + intercepted the crash first. |
| 40 | + |
| 41 | + Both doors are guarded, not one. The `innerMessage` branch and the raw-message |
| 42 | + regex fallback produce byte-identical bodies, so classifying in only one would |
| 43 | + make the envelope depend on whether the `SandboxError` instance survived a |
| 44 | + rethrow. |
| 45 | + |
| 46 | + **Unchanged:** a deliberate refusal still reaches the caller verbatim at 400 |
| 47 | + with no `code`. The fix changes _which_ errors take that branch, not what it |
| 48 | + emits. A body that expresses a business rule as `throw new RangeError('…')` is |
| 49 | + now sanitised — an accepted cost, since that is not the documented authoring |
| 50 | + style and the fail-safe direction is the one that does not ship runtime faults to |
| 51 | + clients. The operator still gets the full text: 500 is outside |
| 52 | + `isExpectedDataStatus`, so `handleRouteError` logs `[REST] Unhandled error` with |
| 53 | + the whole error. |
| 54 | + |
| 55 | + **Showcase.** `NormalizeTaskTitleHook` guarded its trim with truthiness |
| 56 | + (`if (ctx.input.title)`), so the number `12345` passed the guard and had no |
| 57 | + `.trim`. It now checks `typeof … === 'string'`. That is the actual cause of the |
| 58 | + reported repro, and with it fixed the request **succeeds** rather than erroring: |
| 59 | + `record-validator` coerces a `text` value with `String(value)`, so a number in a |
| 60 | + text field breaks no declared contract. These hook bodies are read as |
| 61 | + documentation, so the type-safe shape is the one to show — a hook must not assume |
| 62 | + a field's runtime type just because its metadata declares one. |
| 63 | + |
| 64 | +- d62f8eb: feat(spec)!: refuse inline credentials at publish — driver `config.password` / `config.authToken` and connector `authentication` on authored entries (#7990) |
| 65 | + |
| 66 | + `sys_metadata.metadata` is served back by the ordinary data API, and a datasource or |
| 67 | + connector artefact is persisted whole — so any schema that _accepted_ an inline |
| 68 | + credential stored that credential in cleartext at rest. The maintainer-ruled fix |
| 69 | + (#7990, Option A: per-artefact contract closure) makes the two measured surfaces |
| 70 | + refuse the inline form at publish and divert to the mechanisms that already exist. |
| 71 | + |
| 72 | + **Driver config (postgres / mysql / mongo / turso).** `config.password` (SQL/mongo) |
| 73 | + and `config.authToken` (turso) are now declared-unwritable: writing one fails `tsc` |
| 74 | + (the input type is `never`) and fails the parse with a prescription naming the |
| 75 | + replacement. The former alias spellings (`passwd`, `pwd`, `token`, `jwt`, |
| 76 | + `auth_token`, `authtoken`) carry the same refusal. The connection form's masked |
| 77 | + secret input is unaffected — it never wrote `config`; it feeds the datasource secret |
| 78 | + binder, which encrypts into `sys_secret` and stores only an opaque handle. |
| 79 | + |
| 80 | + **Connector authoring door.** `DeclarativeConnectorEntrySchema` (behind |
| 81 | + `defineStack({ connectors })` and `PUT /meta/connector/:name`) now refuses a |
| 82 | + non-`none` `authentication` on **every** authored entry — catalog descriptors |
| 83 | + included. Until now only provider-bound instances were covered (ADR-0097 §3), so a |
| 84 | + descriptor could publish an inline `token`/`key`/`password`/`clientSecret`. The |
| 85 | + runtime shape is unchanged: a plugin handing resolved secrets to |
| 86 | + `registerConnector` keeps working. |
| 87 | + |
| 88 | + ## FROM → TO |
| 89 | + |
| 90 | + ```ts |
| 91 | + // before — accepted, stored in cleartext in sys_metadata |
| 92 | + defineDatasource({ |
| 93 | + name: "warehouse", |
| 94 | + driver: "postgres", |
| 95 | + config: { database: "analytics", username: "ro", password: "hunter2" }, |
| 96 | + }); |
| 97 | + |
| 98 | + // after — the secret lives in the secret store; config carries no credential |
| 99 | + defineDatasource({ |
| 100 | + name: "warehouse", |
| 101 | + driver: "postgres", |
| 102 | + schemaMode: "external", |
| 103 | + config: { database: "analytics", username: "ro" }, |
| 104 | + external: { allowWrites: false, credentialsRef: "sys_secret:<handle>" }, |
| 105 | + }); |
| 106 | + // (Setup → Datasources binds the secret for you: its password field encrypts into |
| 107 | + // sys_secret and writes external.credentialsRef — it never wrote config.) |
| 108 | + ``` |
| 109 | + |
| 110 | + ```ts |
| 111 | + // before — descriptor published an inline credential |
| 112 | + defineConnector({ |
| 113 | + name: "erp", |
| 114 | + label: "ERP", |
| 115 | + type: "saas", |
| 116 | + authentication: { type: "api-key", key: "…", headerName: "X-API-Key" }, |
| 117 | + }); |
| 118 | + |
| 119 | + // after — descriptor: no live credentials (document the scheme in prose); |
| 120 | + defineConnector({ |
| 121 | + name: "erp", |
| 122 | + label: "ERP", |
| 123 | + type: "saas", |
| 124 | + description: "Authenticates with an API key in the X-API-Key header.", |
| 125 | + }); |
| 126 | + // instance: reference the credential (ADR-0097 §3) |
| 127 | + defineConnector({ |
| 128 | + name: "erp", |
| 129 | + label: "ERP", |
| 130 | + type: "saas", |
| 131 | + provider: "openapi", |
| 132 | + providerConfig: { spec: "./erp-openapi.json" }, |
| 133 | + auth: { type: "api-key", credentialRef: "ERP_API_KEY" }, |
| 134 | + }); |
| 135 | + ``` |
| 136 | + |
| 137 | + There is deliberately **no automatic rewrite**: moving a cleartext credential into |
| 138 | + `sys_secret` requires encrypting it through a running secret binder, which a |
| 139 | + source-file transform cannot do — auto-deleting the key would silently drop a live |
| 140 | + credential instead. `os migrate meta` surfaces both changes as structured TODOs |
| 141 | + (semantic entries `datasource-config-inline-credential-refused`, |
| 142 | + `connector-inline-authentication-publish-refused`). The migration story for |
| 143 | + **already-stored** cleartext rows is programme scope, tracked as a follow-up card |
| 144 | + under #7990 — this release closes the doors that keep writing new ones. |
| 145 | + |
| 146 | + <!-- adr-0087: registered datasource-config-inline-credential-refused, connector-inline-authentication-publish-refused --> |
| 147 | + |
| 148 | +- Updated dependencies [690ccf2] |
| 149 | +- Updated dependencies [333a374] |
| 150 | +- Updated dependencies [30536e3] |
| 151 | +- Updated dependencies [5823d59] |
| 152 | +- Updated dependencies [3140f9c] |
| 153 | +- Updated dependencies [9500ba4] |
| 154 | +- Updated dependencies [76d74ec] |
| 155 | +- Updated dependencies [0e79785] |
| 156 | +- Updated dependencies [86f7a20] |
| 157 | +- Updated dependencies [c546c89] |
| 158 | +- Updated dependencies [627e65a] |
| 159 | +- Updated dependencies [22df871] |
| 160 | +- Updated dependencies [9c82146] |
| 161 | +- Updated dependencies [744b8f5] |
| 162 | +- Updated dependencies [37785ed] |
| 163 | +- Updated dependencies [9d1d9c7] |
| 164 | +- Updated dependencies [1007379] |
| 165 | +- Updated dependencies [4ff8abf] |
| 166 | +- Updated dependencies [e38db3d] |
| 167 | +- Updated dependencies [37b82ed] |
| 168 | +- Updated dependencies [2c1988c] |
| 169 | +- Updated dependencies [211abdb] |
| 170 | +- Updated dependencies [b3de0dd] |
| 171 | +- Updated dependencies [35b36f2] |
| 172 | +- Updated dependencies [19aaf4b] |
| 173 | +- Updated dependencies [0e4a7fb] |
| 174 | +- Updated dependencies [f505689] |
| 175 | +- Updated dependencies [76682cb] |
| 176 | +- Updated dependencies [606d577] |
| 177 | +- Updated dependencies [a5d3aa1] |
| 178 | +- Updated dependencies [e906126] |
| 179 | +- Updated dependencies [08363a0] |
| 180 | +- Updated dependencies [444de5b] |
| 181 | +- Updated dependencies [6a9dec6] |
| 182 | +- Updated dependencies [baeb4f0] |
| 183 | +- Updated dependencies [199ec47] |
| 184 | +- Updated dependencies [08cd163] |
| 185 | +- Updated dependencies [7dc1067] |
| 186 | +- Updated dependencies [7674859] |
| 187 | +- Updated dependencies [b85cc54] |
| 188 | +- Updated dependencies [7a8476f] |
| 189 | +- Updated dependencies [518ca7a] |
| 190 | +- Updated dependencies [d62f8eb] |
| 191 | +- Updated dependencies [a7586cd] |
| 192 | +- Updated dependencies [4c5e80e] |
| 193 | +- Updated dependencies [4b5702a] |
| 194 | +- Updated dependencies [af05400] |
| 195 | +- Updated dependencies [d063a96] |
| 196 | +- Updated dependencies [cf7c694] |
| 197 | +- Updated dependencies [603cab8] |
| 198 | +- Updated dependencies [df95346] |
| 199 | +- Updated dependencies [591f675] |
| 200 | +- Updated dependencies [9051802] |
| 201 | +- Updated dependencies [2f8328c] |
| 202 | +- Updated dependencies [f293d45] |
| 203 | +- Updated dependencies [f067930] |
| 204 | +- Updated dependencies [97ace2a] |
| 205 | +- Updated dependencies [ef7b5ef] |
| 206 | +- Updated dependencies [8f1851e] |
| 207 | +- Updated dependencies [b4b2c7d] |
| 208 | +- Updated dependencies [61ea810] |
| 209 | +- Updated dependencies [66d99ec] |
| 210 | +- Updated dependencies [91eddca] |
| 211 | +- Updated dependencies [b61afc1] |
| 212 | +- Updated dependencies [95ef5c0] |
| 213 | +- Updated dependencies [97b6658] |
| 214 | +- Updated dependencies [0410522] |
| 215 | +- Updated dependencies [814db6d] |
| 216 | +- Updated dependencies [477195c] |
| 217 | +- Updated dependencies [8dd98bf] |
| 218 | +- Updated dependencies [8a9c079] |
| 219 | +- Updated dependencies [cc3555e] |
| 220 | +- Updated dependencies [ea936f3] |
| 221 | +- Updated dependencies [69ac82c] |
| 222 | +- Updated dependencies [833ed84] |
| 223 | +- Updated dependencies [86d2e5e] |
| 224 | +- Updated dependencies [c6a4eeb] |
| 225 | +- Updated dependencies [f450ae7] |
| 226 | +- Updated dependencies [e124711] |
| 227 | +- Updated dependencies [3bb9340] |
| 228 | +- Updated dependencies [f1544e2] |
| 229 | +- Updated dependencies [9d4dfc4] |
| 230 | +- Updated dependencies [1059965] |
| 231 | +- Updated dependencies [ee264b2] |
| 232 | +- Updated dependencies [60b672e] |
| 233 | +- Updated dependencies [e654bfd] |
| 234 | +- Updated dependencies [6e6c872] |
| 235 | +- Updated dependencies [fa5758e] |
| 236 | +- Updated dependencies [ecf0bef] |
| 237 | +- Updated dependencies [3da3da5] |
| 238 | +- Updated dependencies [a0fdc56] |
| 239 | +- Updated dependencies [0dcbc11] |
| 240 | +- Updated dependencies [b9f930b] |
| 241 | +- Updated dependencies [c9b809f] |
| 242 | +- Updated dependencies [32386f8] |
| 243 | +- Updated dependencies [a1dd1e4] |
| 244 | + - @objectstack/runtime@17.0.0-rc.7 |
| 245 | + - @objectstack/spec@17.0.0-rc.7 |
| 246 | + - @objectstack/service-datasource@17.0.0-rc.7 |
| 247 | + - @objectstack/driver-sql@17.0.0-rc.7 |
| 248 | + - @objectstack/cloud-connection@17.0.0-rc.7 |
| 249 | + - @objectstack/connector-mcp@17.0.0-rc.7 |
| 250 | + - @objectstack/connector-openapi@17.0.0-rc.7 |
| 251 | + - @objectstack/connector-rest@17.0.0-rc.7 |
| 252 | + - @objectstack/connector-slack@17.0.0-rc.7 |
| 253 | + |
3 | 254 | ## 0.3.14-rc.5 |
4 | 255 |
|
5 | 256 | ### Patch Changes |
|
0 commit comments