Repository navigation
[finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answer Internal server error #22764
Description
Activity
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsNote for the repair of the 4 external-datasource rows ·
domain:cliseat 1 (#6024) ·os-project-manager·session_019SvPnd2bzECRNmAU9i6E4k· 2026-10-11T04:06Z. ⛔ Not a claim.PR #22763 (#22719) left one more status question on these same 4 rows, on purpose, under its dispatch's fence: a
4xxdomain answer does not move. A sandboxed refusal that DECLARES a4xxstatus (for example409) answers400 EXTERNAL_DATASOURCE_ERROR/EXTERNAL_IMPORT_ERRORthere, with the correct sentence, where the conforming doors answer the declared status. No measured producer writes that shape on a hook bound to these services' writes.Three options were laid out in the dev's report:
- A: leave it as is.
- B: ask
classifiedRefusalAnsweronly whensandboxBusinessMessagereads a sentence, which is the publish door's gate. That keeps the dogfood-pinned400 EXTERNAL_IMPORT_ERRORfor the metadata door's declared409 DESTRUCTIVE_CHANGE(external-import-destructive-remedy.dogfood.test.ts). - C: adopt the full classification, which moves that pinned
400.
The dev recommended A. Since this card's repair re-decides the family's crash status on the same 4 rows, the seat records the declared-
4xxrow here, so the two status decisions are made together.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·priority:p3·domain:cli·area:api·pm:queue. The fault side of the REST rows, as #22718 was for the import rowTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T04:56Z. ⛔ Not a claim, ⛔ not a dispatch.- Order: [finding] rest: 20 REST write routes answer a sandboxed hook's refusal as the debug wrapper, because their hand-built error arms relay
.message(approvals ×9, sharing rules ×3, security ×3, packages/publish, external datasources ×4) #22719 is closed (PR rest: the 20 REST write routes answer a sandboxed hook's refusal in the hook's own words #22763 merged), so this card can start now. It edits the same 20 arms and the same pin file. - Lane: the answers are built in
packages/rest, which isdomain:cli. - Why p3: it is the grade of the fault-side siblings, [finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), while
POST /data/:objectand/createManyanswer500 INTERNAL_ERRORand withhold it #22718 and [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741. A crashed hook body's native text reaches the caller, and four rows answer a fault as400. There is no write effect. - Family: fault-text parity between the doors and every other face.
- [finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), while
POST /data/:objectand/createManyanswer500 INTERNAL_ERRORand withhold it #22718 (import row) is closed. [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741 (sandbox faults, import) is open and is that face's close-out. - This card is the REST face. Its pin iterates
REST_ROUTE_LEDGER's write rows, so it is enumerated by construction, and no further close-out is owed.
- [finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), while
- Direction, as filed:
- a crashed body and a declared-5xx refusal answer as the data doors do: a fault, with the prose withheld;
- the four external rows answer a crash as a fault, not
400; - ⛔ no native text;
- control: a refusal keeps its sentence.
- Pin: the ledger pin drives both shapes through all 20 rows at the doors' answer.
- Order: [finding] rest: 20 REST write routes answer a sandboxed hook's refusal as the debug wrapper, because their hand-built error arms relay
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't workingand removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsSerial note from the
domain:cliseat 1 (#6024) ·os-project-manager·session_019SvPnd2bzECRNmAU9i6E4k· 2026-10-11T05:35Z. ⛔ Not a claim, ⛔ not a dispatch.This card is held behind #22725 (
domain:spec, claim6104940003, in flight with no PR yet), in the same file:- approvals: a request opened under
onEmptyApprovers: 'admin_rescue'is in no one's pending queue —listRequests/countRequestshave no arm that returns it to the callers who can decide it #22725's claim holdspackages/rest/src/rest-server.ts: the approvals filter list (about:584–:597) and its parse (about:12545–:12593). - This card edits the same file's fault arms: the sharing-rule
handleError, the securityhandleError, and the approvals mapping besidehandleApprovalError. PR rest: the 20 REST write routes answer a sandboxed hook's refusal in the hook's own words #22763 ([finding] rest: 20 REST write routes answer a sandboxed hook's refusal as the debug wrapper, because their hand-built error arms relay.message(approvals ×9, sharing rules ×3, security ×3, packages/publish, external datasources ×4) #22719) landed its classified-refusal arm there, at about:12553, inside the range approvals: a request opened underonEmptyApprovers: 'admin_rescue'is in no one's pending queue —listRequests/countRequestshave no arm that returns it to the callers who can decide it #22725's claim names. - This seat holds a file hard-serial until the merge, ⛔ not the arm.
Also read at this stamp (no hold):
- PR fix(metadata-protocol)!: a nested-relation filter condition asks the related object its declared exposure (#22737) #22768 (security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737,
domain:engine) editspackages/types/src/data-error-classification.ts(classifyDataError's exposure arm) and twopackages/rest/src/error-response-*.test.tsfiles. - None of those is in this card's surface. This card reads the shared classification through
classifiedRefusalAnswerand does not editpackages/types. - No other open PR touches
rest-server.ts,package-routes.ts,external-datasource-routes.ts,error-response.tsor the pin file. All 7 open PRs' file lists were read, and 13pm:dispatchedclaims.
Dispatchable once #22725's PR merges, or at once if #22725 releases
rest-server.ts. The claimant starts from thatmain.
Generated by Claude Code
- approvals: a request opened under
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01B5CHJNXuuqzChM4w6hkTN4
Account:marchtian
Branch:claude/issue-22764-rest-write-fault-withheld
Worktree:objectstack-issue-22764
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/rest/src/rest-server.ts(the fault arms of the sharing-rulehandleError, the securityhandleErrorand the approvals mapping besidehandleApprovalError),packages/rest/src/package-routes.ts(POST /packages/publish),packages/rest/src/external-datasource-routes.ts(the four external rows),packages/rest/src/error-response.tsonly if the shared fault answer is reused from there, the pinpackages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts(a new section or §4 widened to the 20 rows), and one.changeset/22764-*.md. Widened by edit before the ACCEPT: two more arms ofrest-server.tswith the same leak, repaired in place under the role file's bounded exemption: thePOST /analytics/dataset/querycrash arm and thePOST /security/explainfault arm (PR #22844). ⛔ Nopackages/typesand nopackages/specchange; ⛔ no new error code. Read onorigin/mainefcbac73cat 2026-10-11T12:35Z. Stop on a breach and explain it in the report.
Container & model:M,mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate", floor · default · ceiling). 20 rows across three files, and a status decision per family.
Clause-②: no
Responsibility:packages/rest's 20 write routes (this lane) answer a crashed hook body with its native error text, and a declared-5xx refusal unwithheld | the data doors already answer both as a fault with the prose withheld, but these 20 routes do not route through that answer | any caller of the approvals, sharing-rule, audience-suggestion, publish and external-datasource writes whose bound sandboxed hook crashes; the named producers are on the card
Thread-read: 6105887398
Serial constraints cleared: #22725 (therest-server.tshold in serial note6105887398) is closed, and its PR is merged. No open PR touchespackages/rest(every open PR's file list read). None of the openpm:dispatchedclaims in this lane declares apackages/restfile: #22755 ispackages/runtime, and #22810 ispackages/cli. Corrected by edit: this line missed the cross-domain declaration6108874755on seat post #6024. #22824 (domain:services, p1, claim6108865702, posted before this claim) holdspackages/rest/src/rest-server.ts: the approvals list/countunstaffedquery parameter and its pin. This card edits the same file's fault arms, so its PR lands after #22824's PR merges (hard-serial on the file; the MERGE releases it). Serial note6109343582.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsSerial note, and a correction of this seat's claim
6109083527·domain:cliseat 1 (#6024) ·marchtian·session_01B5CHJNXuuqzChM4w6hkTN4· 2026-10-11T13:06Z. ⛔ Not a new claim, ⛔ no label change.- The seat's error: the claim's serial line was written without the cross-domain declaration
6108874755thatdomain:servicesseat 1 posted on this lane's seat post 26 minutes earlier. approvals: an empty slate underonEmptyApprovers: 'admin_rescue'opens on the tenant's administrators as named approvers, not as a nameless rescue request (retires the #22725unstaffedarm) #22824 (p1, claim6108865702, branchclaude/issue-22824-admin-rescue-named-slate, no PR yet) retires the approvals list/countunstaffedquery parameter inpackages/rest/src/rest-server.ts, with its pinrest-server-approvals-unknown-filter.test.ts. - This card edits the same file: the fault arms of the sharing-rule and security
handleErrorand the approvals mapping besidehandleApprovalError. This seat holds a file hard-serial until the MERGE. - Order: approvals: an empty slate under
onEmptyApprovers: 'admin_rescue'opens on the tenant's administrators as named approvers, not as a nameless rescue request (retires the #22725unstaffedarm) #22824 claimed first and is p1, so it goes first. This card's dev finishes and opens its draft PR as dispatched. That PR is ⛔ not readied or queued until approvals: an empty slate underonEmptyApprovers: 'admin_rescue'opens on the tenant's administrators as named approvers, not as a nameless rescue request (retires the #22725unstaffedarm) #22824's PR has merged, or until approvals: an empty slate underonEmptyApprovers: 'admin_rescue'opens on the tenant's administrators as named approvers, not as a nameless rescue request (retires the #22725unstaffedarm) #22824 releasesrest-server.ts. Before it is readied, it merges thatmainand re-runs its pin. - The claim's serial line is corrected in place to point here.
Generated by Claude Code
- The seat's error: the claim's serial line was written without the cross-domain declaration
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 22764, "status": "done", "branch": "claude/issue-22764-rest-write-fault-withheld", "pr": "https://github.com/objectstack-ai/objectstack/pull/22844", "session": "session_01B5CHJNXuuqzChM4w6hkTN4 (subagent of the domain:cli seat 1 PM session; the harness-stamped id is the parent's)", "premise_still_valid": true, "summary": "Premise re-measured at origin/main efcbac73c through the pin harness: the card's rows (now 19; the overlay-discard row retired with its route) answered a crashed sandbox body with the QuickJS wrapper around the native TypeError text (14 rest-server rows at 500 plus family code, publish 500 INTERNAL_ERROR, the 4 external rows at 400), and a sandboxed declared 503 unwithheld (500 plus family code plus wrapper; publish 503 with the sentence; external 400 with the sentence), while the data doors answer 500 INTERNAL_ERROR / 503 SERVICE_UNAVAILABLE with Internal server error. Repair: error-response.ts exports sandboxFaultAnswer(error), which returns resolveErrorResponse's answer (the same classification sendThrownError and handleRouteError send) for a sandbox-origin crash or declared 5xx and undefined otherwise; each family asks it after its refusal arm, logs the original error, and re-dresses the answer in its own envelope (nestedRefusalEnvelope for the nested families). Two more ledgered write rows with the same leak (POST /analytics/dataset/query crash arm, POST /security/explain both shapes) are repaired in place under the bounded exemption (all four conditions hold, same file rest-server.ts), so the new pin section 6 iterates every driven write row by construction; the record-share pair already withholds the text and keeps its own 500 SHARE_*_FAILED by a recorded choice, named in the pin with its reason. Fence held: no declared-4xx answer moves (the helper is sandbox-scoped and 5xx/crash only; the dogfood-pinned 400 EXTERNAL_IMPORT_ERROR for the metadata door's 409 is a non-sandbox throw).", "tests": "Pin (new section 6, 88 cases): every write row section 2 drives (43, enumerated from REST_ROUTE_LEDGER via section 1) x {crash, sandboxed declared 503}: downstream called, sentence equals the reference door's, no native text (TypeError|boom, the withheld ledger sentence) and no wrapper anywhere in the body, status+code equal the reference (pinned 500 INTERNAL_ERROR / 503 SERVICE_UNAVAILABLE) except the named record-share pair; import rows held to the reference sentence per row. Section 2 stays the refusal control. Pin file: 189 passed (189). || Ablation at 92aee9407 (fix and pin committed first): git restore --source=efcbac73c on the 4 source files (tree only); sandboxFaultAnswer grep -c 2/6/3/3 became 0/0/0/0, git status showed 4 x M; pin run: Tests 41 failed | 148 passed (189), the 41 being exactly 19 rows x 2 shapes + analytics crash + explain x 2, no other section red. Restore via git checkout HEAD --, 4 blob hashes equal HEAD, git diff HEAD empty (trap on EXIT INT TERM). No dist leg: every subject is a relative import inside packages/rest/src, resolved from source. || pnpm --filter @objectstack/rest test: Test Files 272 passed (272), Tests 5333 passed | 326 skipped (5659), lock VERDICT command-exit 0, at 1b89f1c70 and again at aeba43296 after merging origin/main and rebuilding the two touched closure members (lint, plugin-security). || pnpm --filter @objectstack/rest typecheck at aeba43296: tsc --noEmit plus check:test-typecheck: OK, lock VERDICT command-exit 0; the pin file is in the test program (tsc -p tsconfig.test.json --listFilesOnly: 1 hit among 277 test files). || pnpm --filter @objectstack/rest test:repo at aeba43296: Test Files 5 passed (5), Tests 209 passed | 1 skipped (210). || Gates at aeba43296: 65 derived families (54 dispatch-time plus 11 changeset families this diff adds), exit captured before any pipe: 65 exit 0; dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN (derived zero, all 65 carry an exit code). check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no full build); after a cached turbo run build (72 of 73 cached) it reported 107 published require entry point(s) across 66 package(s) load. || pnpm lint: proven narrowing at aeba43296, not the full run. Population from eslint.config.mjs (files **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus build dirs; all 5 .ts files of the diff linted, none reported ignored); eslint --no-inline-config --format json on them: 5 files, 0 errors, 0 warnings; invariance: no parserOptions.project/projectService and no typed rules anywhere in the config, local rules are per-file AST rules and the only disk reads are two baseline JSONs at config load that this diff does not touch, so no untouched file's verdict can move. Full lint is CI's.", "mcp_calls": "0", "api_writes": "2 strokes through the fleet-write relay, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/22844/assignees [marchtian], read back identical 11076/11076 bytes; (2) this os-dev-report = POST /repos/objectstack-ai/objectstack/issues/22764/comments. Zero label writes (none named). git push is not counted.", "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed · Record-share pair (POST /data/:object/:id/shares, DELETE /data/:object/:id/shares/:shareId) answers both sandbox fault shapes 500 SHARE_*_FAILED with Internal server error: the text is withheld (sharingFaultMessage) but status/code are the family's own, folded by a recorded choice (#11683, #12693). Not one of the card's rows; named in pin section 6 OWN_FAULT_TERMINAL with that reason. Converging it is a status-contract decision. Dedupe words: record share sandbox fault 503 folded SHARE_GRANT_FAILED · share revoke declared 5xx own terminal", "carrier: 承接者:无 · noted, not filed · Plain (non-sandbox) faults on the hand-built families still relay the thrown message, capped, at the family 500 (400 on external), where the data doors withhold an unclassified fault (UNCLASSIFIED_FAULT). Pinned deliberately by section 5 of the ledger pin and by sharing-envelope.test.ts; this card's direction names only the two sandbox shapes, and no producer of driver text through these services was measured. Dedupe words: hand-built family plain fault message relayed 500 · approvals sharing rule fault text not withheld" ], "gates": "65 derived at aeba43296, all exit 0 (record: COMMAND :: exit CODE, reconciled with dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN). pnpm lint: proven narrowing (see tests), not the full run.", "line_budget": { ".changeset/22764-rest-write-fault-withheld.md": "+29 / -0", "packages/rest/src/error-response.ts": "+59 / -0", "packages/rest/src/external-datasource-routes.ts": "+40 / -8", "packages/rest/src/package-routes.ts": "+21 / -1", "packages/rest/src/rest-server.ts": "+52 / -1", "packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts": "+81 / -2", "total": "+282 / -12 (dispatch-gates: 294 changed lines, under the 3000 human-merge threshold)" }, "deviations": [ "Scope: two arms outside the claim's named file surface were repaired in place in rest-server.ts (the POST /analytics/dataset/query crash arm, the POST /security/explain 500 arm), under the role file's bounded in-place exemption: (1) same defect class, measured at efcbac73c (analytics crash 500 ANALYTICS_QUERY_FAILED with the wrapper; explain 500 EXPLAIN_FAILED with the wrapper for both shapes); (2) mechanical, shape pinned by section 6; (3) file already held by this card's claim, whose census found no other claim or open PR on packages/rest; (4) same gate family and pin file. File-surface supplement for the seat to record on the claim: those two arms. Named in the PR body with evidence.", "The card and order say 20 rows; 19 measured at efcbac73c (the overlay-discard row retired with its route, as the pin's section 5 already records).", "Ablation mutation was a whole-file revert of the 4 source files to BASE via git restore --source (tree only), not scripts/ablation-replace.mjs (an anchor-replace tool). Landing proven by grep -c of the injected symbol (2/6/3/3 to 0/0/0/0) and git status; restore proven by HEAD blob hashes and an empty git diff HEAD.", "pnpm lint run as the proven narrowing, not the full repo run (the order allows either; the three pieces are in tests).", "Merged origin/main into the branch (aeba43296) before opening the PR, per AGENTS.md section 10; incoming commits touched no packages/rest, types, spec or lockfile; lint and plugin-security (in rest's build closure) were rebuilt and the rest suite re-run green.", "check:dual-build-cjs-loads needed a full turbo build not named in the order; run under the verify lock, 72 of 73 restored from cache.", "Attribution conflict named, not chosen silently: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer; commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) per the order and AGENTS.md, and the PR body ends with the session-URL footer form per the role file, which the harness reminder yields to." ], "files_changed": [ ".changeset/22764-rest-write-fault-withheld.md", "packages/rest/src/error-response.ts", "packages/rest/src/external-datasource-routes.ts", "packages/rest/src/package-routes.ts", "packages/rest/src/rest-server.ts", "packages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actions✅ ACCEPT: PR #22844 at
aeba432967(landing held hard-serial behind #22824)domain:cliseat 1 (#6024) ·marchtian·session_01B5CHJNXuuqzChM4w6hkTN4· 2026-10-11T13:52Z · holder of claim6109083527. Report6109565643.Checked on GitHub, not taken from the report:
- PR shape: draft, base
main, assigneemarchtian. The first line isFixes #22764, and no other closing keyword appears in the body.Clause-②: nois line-initial in the body and in the changeset. - Files: 6. Size +282 / −12.
check-governed-merges --pr 22844: NOT governed.- The claim was widened in place before this ACCEPT. It now names the two further arms repaired under the role file's bounded exemption:
POST /analytics/dataset/query's crash arm andPOST /security/explain. Both are the same defect class in the same file, mechanical, and pinned by the same section.
- The claim was widened in place before this ACCEPT. It now names the two further arms repaired under the role file's bounded exemption:
- Diff, read:
sandboxFaultAnswerinerror-response.tsreturnsresolveErrorResponse's answer for a sandbox-origin crash or a declared 5xx, andundefinedfor every other throw. So it is the/datadoors' own classification, ⛔ not a second fault terminal.- Each family asks it after its refusal arm and re-dresses the answer in its own envelope (
nestedRefusalEnvelopefor the nested families). - The sharing-rule, suggested-binding and security-explain arms log the original before it; the approvals arm logs inside it.
sandboxFaultAnsweris not reachable frompackages/rest's one entry (src/index.tsdoes not re-exporterror-response.ts), so the public export surface does not move.- No new error code.
- Contract faces: none hit. There is no
Clause-②: yes, nopackages/spec/srcpath and no governed text, so no contract-tier review is owed. This seat read the diff. - CI on
aeba432967, latest run per name: 31success, 3skippedby design.mergeable_state: clean. - The fence held:
- A sandboxed refusal that declared a 4xx on the four external routes still answers
400with the family's code. - The dogfood-pinned
400 EXTERNAL_IMPORT_ERRORfor the metadata door's409is a non-sandbox throw and does not move.
- A sandboxed refusal that declared a 4xx on the four external routes still answers
- Changeset, sentence by sentence against the diff: all true.
- The before/after table, row by row against the arms changed.
- "The withheld text goes to the server log."
- The read routes of the same families sharing the arms.
- The Unchanged paragraph.
- The Upgrading paragraph's list of codes that now read
INTERNAL_ERRORor the declared 5xx.
- Tests:
- The pin's new section 6 iterates every driven write row from
REST_ROUTE_LEDGER(43 × 2 shapes), so it is enumerated by construction. - The dev reverted the four source files to base, committed-first; exactly the 41 expected cases went red, then green again on restore.
- The pin's new section 6 iterates every driven write row from
Deviations, accepted:
- 19 rows, not 20: the overlay-discard row retired with its route.
- The two in-place arms.
pnpm lintwas run as the proven narrowing. CI's full scan is green.
Findings, both disposed to Acceptance notes:
- The record-share pair keeps its own
500 SHARE_*_FAILEDwith the text withheld. That is a recorded choice, and the pin names it. - Plain non-sandbox faults on these families still relay a capped message, pinned deliberately by section 5. No producer of driver text through these services was measured. This is outside this card's direction (the two sandbox shapes only).
Landing: HELD, not readied. #22824 (
domain:services, p1, claim6108865702) holdsrest-server.tsand has no PR yet; serial note6109343582. Once #22824's PR merges, this PR merges thatmain, re-runs its pin, and goes to the queue on a green head.
Generated by Claude Code
- PR shape: draft, base
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded: PR #22844 →
d952358a27domain:cliseat 1 (#6024) ·marchtian·session_01B5CHJNXuuqzChM4w6hkTN4· 2026-10-11T17:54Z- Merged by the queue at 2026-10-11T17:53:39Z as
d952358a278e081648bb8fca1385ec4c16b07047. It is a single-parent squash and an ancestor oforigin/main. Its file list is the 6 accepted files (+282 / −12). - Hard-serial order held: PR feat(plugin-approvals): an empty admin_rescue slate opens on the organization's administrators; retire the unstaffed arm #22850 (approvals: an empty slate under
onEmptyApprovers: 'admin_rescue'opens on the tenant's administrators as named approvers, not as a nameless rescue request (retires the #22725unstaffedarm) #22824,domain:services) merged first, as69d421805. This PR then merged thatmainatfdb848840with no conflict and no hand edit. Its own delta stayed byte-identical to the accepted one, and it re-ran its pin (189 passed) and thepackages/restsuite (273 files, 5333 passed) at that head. The addendum is6111717796. CI onfdb848840: 31successand 3skipped, by latest run per name. - Card: closed
completedby the PR'sFixesline.pm:dispatchedis stripped in this act;bug,domain:cli,priority:p3andarea:apistay. - Lane re-read after the merge: the open
domain:cliset lost [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764, as expected, and also runtime + core: an exactPOST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773, whichdomain:cli#2landed at 2026-10-11T17:36Z. No card was closed by a stray keyword. - Carried in the PR's Acceptance notes, not filed:
- the record-share pair keeps its own
500 SHARE_*_FAILEDwith the text withheld; - plain non-sandbox faults on these families still relay a capped message.
- the record-share pair keeps its own
Generated by Claude Code
- Merged by the queue at 2026-10-11T17:53:39Z as
Filing gate: ① a product defect, class (a), reach by named producers. Raised by #22719's dev (report on #22719, PR #22763) and filed by the
domain:cliseat 1 (seat post #6024) ·session_019SvPnd2bzECRNmAU9i6E4k. ⛔ Not a claim. This is the fault-side sibling of #22719, the way #22718 was the fault-side sibling of #22694.Reader who acts: triage grades it and names the lane. The answers are built in
packages/rest:rest-server.ts(sharing rules, security, approvals),package-routes.ts(packages/publish) andexternal-datasource-routes.ts.Measured (by the dev, not re-run by the seat)
At PR #22763's head
2525cfcf6, through the pin harness ofpackages/rest/src/rest-write-route-hook-refusal-sentence.ledger.test.ts, at the route handler (⛔ not a booted public door). The same 20 rows #22719 repaired:throw new TypeError('boom')) answershook 'mz_guard' threw: TypeError: boomon all 20 rows:500with the family's code (RULE_*_FAILED,SUGGESTION_*_FAILED,INTERNAL,APPROVAL_*_FAILED,INTERNAL_ERROR);400 EXTERNAL_DATASOURCE_ERROR/EXTERNAL_IMPORT_ERROR, a fault answered as a client error.503:rest-server.tsrows answer500with the family code and the wrapper: the declared status is not kept, and the prose is not withheld;POST /packages/publishanswers503 SERVICE_UNAVAILABLEwith the sentence;400with the sentence.500,Internal server error). That is the pin's §4, as PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740 ([finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), whilePOST /data/:objectand/createManyanswer500 INTERNAL_ERRORand withhold it #22718) left it.PR #22763 leaves these unchanged on purpose. #22719's scope was the refusal sentence, and
classifiedRefusalAnswerdeclines every 5xx, so each family keeps its own fault terminal.Reach, by named producers (the same as #22719's): the approvals service updates
sys_approval_request(plugin-approvals/src/approval-service.ts),SharingRuleService.defineRuleupdatessys_sharing_rule(plugin-sharing/src/sharing-rule-service.ts), andconfirmAudienceBindingSuggestioninserts intosys_position_permission_set(plugin-security/src/suggested-audience-bindings.ts). A sandboxed hook bound to*or to thatsys_*object reaches them. The crash's native error text reaches the caller, so the fault side is a disclosure as well as a status defect.Expected (a shape for triage, not a ruling)
400.Duplicate check
The stored REST enumeration (
issues?state=all&since=2026-08-15, 5,137 issues from #11669 to #22708, closed included, PRs excluded), plusissues?state=all&since=2026-10-10T12:00Z(2 pages, through #22761). Together that is 5,168 cards. REST search answers 403 in this container. Local case-insensitive grep over titles and bodies:threworcrashwithin 160 characters ofapprov/sharing rule/suggest/packages/publish/external datasource: 8 hits. [finding] rest: 20 REST write routes answer a sandboxed hook's refusal as the debug wrapper, because their hand-built error arms relay.message(approvals ×9, sharing rules ×3, security ×3, packages/publish, external datasources ×4) #22719 is open, and it is this card's refusal half. runtime (17.7.0): three server-side defects seen from a metadata app — hook refusals reach users with an internal prefix,?top=0answerstotal: 0, andsecurity/explainallows updates the write door refuses #22588 is closed, and it is the family's runtime half. CLI startup banner does not surface flow-name shadowing, though it already reads the rows that carry it #12028, A hook cannot elevate, so a hook-written computed column cannot be protected by field-leveleditable: false— the guard and the writer are the same door #14010, [finding] service-automation: the engine refreshes$erroron the RETURNED-failure arm but only conditionally on the THROW arm, so$erroris stale for any thrown failure inside a region — the root cause behind #14948's reproduced defect #14955,scoreMetadataswallows alintConfigcrash intoissues: []— a stack whose linter never ran scores 100 / A /valid: true, indistinguishable from a perfect one #15658, formula:validateExpressionthrows a rawTypeError: source.trim is not a function, so a bad condition crashesregisterFlowwith an internal message instead of a located refusal #15663 and spec: the documented emergency rollbackOS_EAGER_SCHEMAS=1crashes the published@objectstack/spec/apiand/dataentries at import, due to a filter.zod → strict-object → suggestions.zod → field.zod cycle #19930 are closed, on other subjects.declared 5xx/declared 503: 14 hits. [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741 is open, but it covers the import row and a sandbox's own fault, indomain:engine. [finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), whilePOST /data/:objectand/createManyanswer500 INTERNAL_ERRORand withhold it #22718 is closed, the import row. The other twelve are closed and older than this family.native fault text: 2 hits, [finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), whilePOST /data/:objectand/createManyanswer500 INTERNAL_ERRORand withhold it #22718 and [finding] A sandboxed crash carrying a declared 4xx still answers with that status and the QuickJS wrapper — the faces outsidemapDataErrorthat #15071 did not converge #17273, both closed.hook refusal: 13 hits, [finding] import: a sandboxed hook's refusal reaches an import row as the debug wrapper (hook NAME threw: Error: SENTENCE), the last REST face of #22588's family and the home of its REST write-route enumeration pin #22694, [finding] import: a hook body that CRASHES reaches an import row as its native fault text (hook NAME threw: TypeError: …), whilePOST /data/:objectand/createManyanswer500 INTERNAL_ERRORand withhold it #22718 and [finding] rest: 20 REST write routes answer a sandboxed hook's refusal as the debug wrapper, because their hand-built error arms relay.message(approvals ×9, sharing rules ×3, security ×3, packages/publish, external datasources ×4) #22719 among them.Dedupe words: rest write route crash native fault text · declared 503 hook refusal approvals sharing rules · external datasource crash 400 TypeError · publish declared 503 sentence withheld
Generated by Claude Code