Skip to content

Commit cccfc9f

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24579-v18-sweep-accuracy-round4
2 parents 2c7430d + bfc1539 commit cccfc9f

22 files changed

Lines changed: 1410 additions & 234 deletions
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/service-storage': minor
3+
---
4+
5+
fix(service-storage): the single-shot upload commit refuses a file whose bytes the storage backend does not hold
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) What narrows is the single-shot upload commit door: a completion naming a file whose bytes the storage backend does not hold, or holds at another size than the upload declared, used to answer 200 and commit the row, and is now refused 409 with the row left pending. Nothing migrates: no spec key, authorable spelling, export or stored shape is removed, renamed or re-shaped, and no stored row is read, rewritten or converted, so there is nothing for `objectstack migrate meta` to rewrite. A refused client finishes the upload by sending its bytes, not by rewriting code or metadata. -->
10+
11+
**What was wrong.** `POST /api/v1/storage/upload/complete` set the `sys_file` row to `committed` without asking the storage backend. A presigned upload whose byte `PUT` failed, or was never sent, therefore committed with the size it declared. The download doors serve any `committed` file, so `GET /api/v1/storage/files/:fileId/url` handed out a signed URL, and fetching it failed with `500`.
12+
13+
**What the commit door does now.** Before it commits, it asks the backend through two members every adapter implements: `exists(key)`, and `getInfo(key).size` compared with the size the upload declared. When the backend holds no bytes for the file, or holds a different number of bytes, the door answers `409` with the standard code `RESOURCE_CONFLICT`. The row stays `pending`, so nothing is served for it. This is the answer the chunked completion already gives an upload that does not hold its declared bytes.
14+
15+
**What changes for you.** Nothing, if your client sends the bytes before it completes, as the SDK's `storage.upload` and the console's upload provider do. A client that completes first now receives the `409`: send the file to its upload URL (start a new upload if that URL has expired), then complete again. If the storage adapter reports a fault while the door asks, the door answers `500`. The local adapter does not report an unreadable storage root as a fault: it reports it as holding no bytes, so on the local adapter the door answers `409`.
16+
17+
The `eTag` a completion may carry is still stored as sent and not compared: no adapter reports the stored object's ETag.
18+
19+
**The local adapter's download link.** `GET /api/v1/storage/_local/raw/:token` answers `404` `FILE_NOT_FOUND` when no file is stored under the link's key. Before, it answered `500` `INTERNAL` ("Download failed"). Every other read failure still answers `500`.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/service-automation': patch
3+
---
4+
5+
The flow inspector's Error Variable help for a Try / Catch node describes the caught error in words
6+
7+
Clause-②: no (help text only: the `try_catch` descriptor's `configSchema` accepts exactly what it accepted before, and the value bound to the error variable is unchanged)
8+
9+
- The `errorVariable` description no longer names a TypeScript type (`TryCatchErrorValue`). It now says what the bound value carries, in plain language: the id of the Try / Catch node (`nodeId`), the failure message (`message`), the platform error code when the failing step reported one (`code`), and, for a failure inside a loop, the zero-based pass number (`iteration`) and the item being processed (`item`). The default variable `$error` is named.
10+
- The old text also left out `code`, which the engine has bound since the classified-code change; an author reading the inspector had no way to learn that a catch region can branch on it.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
---
4+
5+
fix(plugin-approvals): the approval status mirror writes only a value the field declares, so a recalled record whose status field is a select goes back to where it stood before it entered approval
6+
7+
Clause-②: no
8+
9+
**The rule:** when `approvalStatusField` is an option field (a `select`) whose options do not declare the status, a recall or the dead-run sweep writes the value the field held when the flow started (the request's submit snapshot), else the field's declared default, else empty, and every other transition writes nothing and logs a `warn` naming the object, field and value.
10+
11+
- **Before.** Each status was written verbatim. The engine checks a `readonly` field's value for shape only, and the spec advises a readonly status field, so a select that did not declare `recalled` ended up holding it after a recall. An app whose submit gate admits only the select's own values (for example `not_required` or empty) then never re-entered approval. On an editable select the engine refused the undeclared value, and the field kept reading `pending` with no request pending.
12+
- **Now.** A recalled record, or one released by the dead-run sweep, reads its submit-time value again, so the app's own start condition decides whether it re-enters approval. A gate that admits that value re-admits the record at once, because the restore is itself an update.
13+
- **Send-back keeps the field.** A select that does not declare `returned` keeps reading `pending` through the revise window. The run is still alive there (ADR-0044), and a restored pre-approval value would re-admit the start condition and open a second, parallel approval of the same record.
14+
- **Unchanged.** A status the select declares is written as before. A field with no declared value set (text, or any type that is not an option type) gets every status verbatim, as before. The write's context is unchanged.

0 commit comments

Comments
 (0)