Repository navigation
Commit d952358
Fixes #22764
Clause-②: no
The hand-built REST write routes now answer two sandboxed-hook failures
the way the `/data` routes do: a hook body that **crashed**, and a hook
refusal that **declared a server-band status**. Both are faults, and the
text is withheld. A hook's refusal still keeps its sentence on every
row.
## What was wrong (re-measured before any edit)
Measured at `origin/main` `efcbac73c`, at the route handler, through the
pin harness of
`packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts`.
The two shapes were a crashed body (`.innerMessage` = `TypeError: boom`)
and a sandboxed refusal declaring `status: 503, code:
'SERVICE_UNAVAILABLE'`:
- **The reference.** `POST /data/:object` and `/createMany` answer a
crash `500 INTERNAL_ERROR` `Internal server error`. They answer the
declared 503 as `503 SERVICE_UNAVAILABLE` `Internal server error`.
- **The card's rows, now 19.** The overlay-discard row retired with its
route after the card was filed.
- The 14 sharing-rule, suggested-binding and approval rows answered a
crash with `500` + the family code (`RULE_*_FAILED`,
`SUGGESTION_*_FAILED`, `APPROVAL_*_FAILED`) and `hook 'mz_guard' threw:
TypeError: boom`. They answered the declared 503 with the same `500` +
family code and the wrapper.
- `POST /packages/publish` answered a crash `500 INTERNAL_ERROR` with
the wrapper, and the declared 503 as `503 SERVICE_UNAVAILABLE` with the
hook's sentence.
- The 4 external-datasource rows answered a crash `400
EXTERNAL_DATASOURCE_ERROR` / `400 EXTERNAL_IMPORT_ERROR` with the
wrapper, and the declared 503 at the same `400` with the sentence.
- **Two more ledgered write rows, same defect** (measured in the same
pass over every driven row):
- `POST /analytics/dataset/query` answered a crash `500
ANALYTICS_QUERY_FAILED` with the wrapper. Its declared-503 answer
already matched the reference.
- `POST /security/explain` answered both shapes `500 EXPLAIN_FAILED`
with the wrapper.
- **Every other driven write row already matched the reference.** The
only exception is the record-share pair, which withholds the text but
keeps its own `500 SHARE_*_FAILED` (see Acceptance notes).
## The repair: one shared answer, re-dressed per family
`error-response.ts` gains `sandboxFaultAnswer(error)`. It is the
classification door's own answer (`resolveErrorResponse`, the same one
`sendThrownError` / `handleRouteError` send) for a sandbox-origin error
that crashed or declared a 5xx, and it answers `undefined` for anything
else. It is not a second fault implementation. It is the counterpart of
`classifiedRefusalAnswer`, which answers the refusal half and declines
every 5xx.
Each family asks it after its refusal arm and before its own 500
terminal. It re-dresses the answer in the family's own envelope:
`nestedRefusalEnvelope` for the nested families, the flat body for
analytics. It logs the original error first, because the operator is now
the only reader of the native text. The publish and external-datasource
doors gained that log line. The approvals helper logs inside itself,
because answering skips the outer arm's log.
| rows | crash, after | declared 503, after |
| --- | --- | --- |
| all 19 card rows, `POST /security/explain` | `500 INTERNAL_ERROR`,
`Internal server error` | `503 SERVICE_UNAVAILABLE`, `Internal server
error` |
| `POST /analytics/dataset/query` | `500 INTERNAL_ERROR`, `Internal
server error` (its flat `{ code, error }`) | unchanged (already the
reference) |
Envelopes: sharing-rule, approvals, explain, publish and external use
the shared nested `{ success: false, error: { code, message } }`.
Suggested bindings use their family's `{ error: { code, message } }`. A
`userMessage` and a demoted `declaredCode` ride the answer, as at the
doors. The same arms also serve `GET /sharing/rules`, `GET
/sharing/rules/:idOrName`, `GET /security/suggested-bindings` and `GET
/datasources/:name/external/tables`, so those reads answer the two
shapes the same way.
**Codes.** PM assumption 3 asked whether a family keeps its own code on
a fault. Following the card's direction ("as the data doors do"), these
two shapes now answer the doors' code. Every family code stays live for
every other fault, and §5 of the pin still holds the 19 rows'
plain-fault terminal at `RULE_*_FAILED` / `APPROVAL_*_FAILED` /
`SUGGESTION_*_FAILED` / `INTERNAL_ERROR` / `EXTERNAL_*`. So the ledger
entries (`content/docs/references/api/error-code-ledger.mdx`,
auto-generated) keep their emitters and nothing in `packages/spec`
moves. No test outside this pin asserted a family code on either sandbox
shape: the full rest suite is green with no edit to any other test.
## Scope
- **The two extra rows** (`POST /analytics/dataset/query` crash arm,
`POST /security/explain` 500 arm) are repaired here under the bounded
in-place exemption. All four conditions hold:
- ① The defect class is the same: a crashed sandbox body's native text,
or a declared 5xx unwithheld, on a ledgered REST write row.
- ② The repair is mechanical, and §6 pins its shape: the same helper and
the doors' answer.
- ③ The file is `rest-server.ts`, which this card's claim already holds.
The claim's census records no open PR or other claim on `packages/rest`.
- ④ The gate family is the same, with no new verification surface: §6 of
the same pin.
**File-surface supplement to the claim:** the analytics dataset-query
fault arm and the security-explain fault arm, both in `rest-server.ts`.
Without them, §6 could not enumerate the ledger's write rows by
construction, which the triage grade's pin asks for.
- **Fence held (the declared-4xx question, options A/B/C).** Nothing
here moves a declared-4xx answer. The helper is sandbox-scoped and
answers only a crash or a declared 5xx. On the four external rows a
sandboxed refusal declaring a 4xx, and a non-sandbox declared 4xx, still
answer `400` with the family's code: §5's declared-409 cases pass
unchanged. The metadata door's `409 DESTRUCTIVE_CHANGE` that
`external-import-destructive-remedy.dogfood.test.ts` pins at `400
EXTERNAL_IMPORT_ERROR` is a non-sandbox throw and does not reach the
helper. That is option A, the status quo.
- **Not widened to non-sandbox declared 5xx.** A service's own declared
5xx keeps each route's terminal. On publish, that terminal's prose
withhold is byte for byte its dispatcher twin's heuristic. A census of
`plugin-approvals`, `plugin-sharing` and `plugin-security` source finds
no declared 5xx. `service-package`'s one (`packageSeamUnreadableError`,
503) is a seat refusal answered by that twin rule, so it is left alone.
## Tests
- **The pin, §6** (new): every write row §2 drives, enumerated from
`REST_ROUTE_LEDGER` through §1, with both shapes, 88 cases (43 rows x 2
+ the pinned reference + a map-membership check). Each case asserts:
- the named downstream was called;
- the answer's sentence equals the reference door's;
- neither the native text (`TypeError|boom`, the withheld `ledger`
sentence) nor the wrapper appears anywhere in the body;
- `status` and `code` equal the reference's (`500 INTERNAL_ERROR` / `503
SERVICE_UNAVAILABLE`, pinned literally on the reference case), except
the record-share pair, which is named with its reason. The two import
rows answer per row inside a `200` / `201` and are held to the reference
sentence.
§2 remains the control: a refusal keeps its sentence on the same rows.
- **Ablation at `92aee9407`** (fix and pin committed first). `git
restore --source=efcbac73c` restored the four source files in the tree
only: the `sandboxFaultAnswer` counts went 2/6/3/3 to 0/0/0/0, and `git
status` showed four ` M`. The pin then ran **41 failed / 148 passed
(189)**. The 41 are exactly 19 rows x 2 shapes + the analytics crash +
the explain crash and declared 503, and no other section went red. The
restore used `git checkout HEAD --`: all four blob hashes equal `HEAD`
and `git diff HEAD` is empty, run under an `EXIT INT TERM` trap. There
is no dist leg: every subject is a relative import inside
`packages/rest/src`, so it resolves from source.
- `pnpm --filter @objectstack/rest test` passed (**272 files, 5333
passed, 326 skipped**, lock `VERDICT command-exit 0`), at `1b89f1c70`.
It passed again at `aeba43296`, after merging `origin/main` and
rebuilding the two touched closure members (`lint`, `plugin-security`).
- `pnpm --filter @objectstack/rest typecheck` passed at `aeba43296`:
`tsc --noEmit` plus `check:test-typecheck: OK`. The pin file is in the
test program (`tsc -p tsconfig.test.json --listFilesOnly`: 1 hit, 277
test files).
- `pnpm --filter @objectstack/rest test:repo` passed at `aeba43296`: 5
files, 209 passed, 1 skipped. Three of those are the `error-response-*`
door-parity pins.
## Gates (at `aeba43296`)
- 65 derived families (the 54 dispatch-time ones, plus 11 changeset
families added by this diff's `.changeset` file), each run with its exit
captured before any pipe: **65 exit 0**. `dispatch-gates --ran`
reconciles: "65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN".
- `check:dual-build-cjs-loads` first answered `PREREQUISITE NOT MET` (no
full build in this worktree). After a cached full `turbo run build` (72
of 73 cached), it reported "107 published require entry point(s) across
66 package(s) load".
- **`pnpm lint`: proven narrowing, not the full run.**
- ① The population is read from `eslint.config.mjs`: `files:
['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']` minus the build dirs. All five
`.ts` files of this diff are in it, since none reported "File ignored".
- ② `eslint --no-inline-config --format json` on those five files gave 5
files, 0 errors and 0 warnings.
- ③ Invariance: the config never enables type-aware linting (no
`parserOptions.project` or `projectService`, no typed rules). Its local
rules are per-file AST rules, and its only disk reads are two baseline
JSONs at config load, which this diff does not touch. So the diff cannot
move any untouched file's verdict.
The full `pnpm lint` run is CI's.
## Acceptance notes
- **Record-share pair** (`POST /data/:object/:id/shares`, `DELETE
/data/:object/:id/shares/:shareId`). For both sandbox fault shapes it
answers `500 SHARE_*_FAILED` with `Internal server error`: the text is
withheld (`sharingFaultMessage`), but the status and code are the
family's own. Its comments record the fold of a declared 5xx into that
500 as deliberate (#11683, #12693). It is not one of the card's rows, so
§6 names it in `OWN_FAULT_TERMINAL` with that reason instead of
converging it. It is noted, not filed.
- **Plain (non-sandbox) faults** on these families still relay the
thrown message, capped, at the family's `500` (`400` on external). §5
pins this, as does `sharing-envelope.test.ts` for the record-share
family, and the arms' comments record it as deliberate. The data doors
withhold an unclassified fault's text instead. This card's direction
names the two sandbox shapes only, so nothing moved. It is noted, not
filed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01B5CHJNXuuqzChM4w6hkTN4)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0253192 commit d952358
6 files changed
Lines changed: 282 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
678 | 678 | | |
679 | 679 | | |
680 | 680 | | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
681 | 736 | | |
682 | 737 | | |
683 | 738 | | |
| |||
713 | 768 | | |
714 | 769 | | |
715 | 770 | | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
716 | 775 | | |
717 | 776 | | |
718 | 777 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
21 | 24 | | |
| 25 | + | |
22 | 26 | | |
23 | 27 | | |
24 | 28 | | |
| |||
396 | 400 | | |
397 | 401 | | |
398 | 402 | | |
399 | | - | |
400 | | - | |
401 | | - | |
402 | | - | |
403 | | - | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
404 | 410 | | |
405 | 411 | | |
406 | 412 | | |
407 | 413 | | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
408 | 441 | | |
409 | 442 | | |
410 | 443 | | |
411 | 444 | | |
412 | 445 | | |
413 | 446 | | |
414 | | - | |
415 | | - | |
| 447 | + | |
416 | 448 | | |
417 | 449 | | |
418 | 450 | | |
| |||
489 | 521 | | |
490 | 522 | | |
491 | 523 | | |
492 | | - | |
| 524 | + | |
493 | 525 | | |
494 | 526 | | |
495 | 527 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
| 22 | + | |
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| |||
202 | 203 | | |
203 | 204 | | |
204 | 205 | | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
205 | 218 | | |
206 | 219 | | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
207 | 227 | | |
208 | 228 | | |
209 | 229 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
414 | 414 | | |
415 | 415 | | |
416 | 416 | | |
| 417 | + | |
417 | 418 | | |
418 | 419 | | |
419 | 420 | | |
| |||
11242 | 11243 | | |
11243 | 11244 | | |
11244 | 11245 | | |
11245 | | - | |
| 11246 | + | |
| 11247 | + | |
| 11248 | + | |
| 11249 | + | |
| 11250 | + | |
| 11251 | + | |
| 11252 | + | |
| 11253 | + | |
| 11254 | + | |
| 11255 | + | |
| 11256 | + | |
11246 | 11257 | | |
11247 | 11258 | | |
11248 | 11259 | | |
| |||
11498 | 11509 | | |
11499 | 11510 | | |
11500 | 11511 | | |
| 11512 | + | |
| 11513 | + | |
| 11514 | + | |
| 11515 | + | |
| 11516 | + | |
| 11517 | + | |
| 11518 | + | |
| 11519 | + | |
| 11520 | + | |
11501 | 11521 | | |
11502 | 11522 | | |
11503 | 11523 | | |
| |||
12117 | 12137 | | |
12118 | 12138 | | |
12119 | 12139 | | |
| 12140 | + | |
| 12141 | + | |
| 12142 | + | |
| 12143 | + | |
| 12144 | + | |
| 12145 | + | |
| 12146 | + | |
| 12147 | + | |
| 12148 | + | |
| 12149 | + | |
12120 | 12150 | | |
12121 | 12151 | | |
12122 | 12152 | | |
| |||
12324 | 12354 | | |
12325 | 12355 | | |
12326 | 12356 | | |
| 12357 | + | |
| 12358 | + | |
| 12359 | + | |
| 12360 | + | |
| 12361 | + | |
| 12362 | + | |
| 12363 | + | |
| 12364 | + | |
12327 | 12365 | | |
12328 | 12366 | | |
12329 | 12367 | | |
| |||
12535 | 12573 | | |
12536 | 12574 | | |
12537 | 12575 | | |
| 12576 | + | |
| 12577 | + | |
| 12578 | + | |
| 12579 | + | |
| 12580 | + | |
| 12581 | + | |
| 12582 | + | |
| 12583 | + | |
| 12584 | + | |
| 12585 | + | |
| 12586 | + | |
| 12587 | + | |
| 12588 | + | |
12538 | 12589 | | |
12539 | 12590 | | |
12540 | 12591 | | |
| |||
0 commit comments