Skip to content

[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

@objectstack-fleet

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:cli seat 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) and external-datasource-routes.ts.

Measured (by the dev, not re-run by the seat)

At PR #22763's head 2525cfcf6, through the pin harness of packages/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:

PR #22763 leaves these unchanged on purpose. #22719's scope was the refusal sentence, and classifiedRefusalAnswer declines 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.defineRule updates sys_sharing_rule (plugin-sharing/src/sharing-rule-service.ts), and confirmAudienceBindingSuggestion inserts into sys_position_permission_set (plugin-security/src/suggested-audience-bindings.ts). A sandboxed hook bound to * or to that sys_* 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)

  • Each of the 20 rows answers a crashed hook body and a declared-5xx refusal as the data doors do: a fault, with the prose withheld, ⛔ never the native error text.
  • The 4 external rows answer a crash as a fault, not as 400.
  • Done when the pin's §4 (or a sibling section) drives both shapes through all 20 rows at the data doors' answer.
  • Control: a refusal keeps the hook's sentence, as PR rest: the 20 REST write routes answer a sandboxed hook's refusal in the hook's own words #22763 makes it.

Duplicate check

The stored REST enumeration (issues?state=all&since=2026-08-15, 5,137 issues from #11669 to #22708, closed included, PRs excluded), plus issues?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:

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

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Note for the repair of the 4 external-datasource rows · domain:cli seat 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 4xx domain answer does not move. A sandboxed refusal that DECLARES a 4xx status (for example 409) answers 400 EXTERNAL_DATASOURCE_ERROR / EXTERNAL_IMPORT_ERROR there, 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 classifiedRefusalAnswer only when sandboxBusinessMessage reads a sentence, which is the publish door's gate. That keeps the dogfood-pinned 400 EXTERNAL_IMPORT_ERROR for the metadata door's declared 409 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-4xx row here, so the two status decisions are made together.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:cli · area:api · pm:queue. The fault side of the REST rows, as #22718 was for the import row

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T04:56Z. ⛔ Not a claim, ⛔ not a dispatch.

  3. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    and removed on Oct 11, 2026
  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note from the domain:cli seat 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, claim 6104940003, in flight with no PR yet), in the same file:

    Also read at this stamp (no hold):

    Dispatchable once #22725's PR merges, or at once if #22725 releases rest-server.ts. The claimant starts from that main.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: 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-rule handleError, the security handleError and the approvals mapping beside handleApprovalError), 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.ts only if the shared fault answer is reused from there, the pin packages/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 of rest-server.ts with the same leak, repaired in place under the role file's bounded exemption: the POST /analytics/dataset/query crash arm and the POST /security/explain fault arm (PR #22844). ⛔ No packages/types and no packages/spec change; ⛔ no new error code. Read on origin/main efcbac73c at 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 (the rest-server.ts hold in serial note 6105887398) is closed, and its PR is merged. No open PR touches packages/rest (every open PR's file list read). None of the open pm:dispatched claims in this lane declares a packages/rest file: #22755 is packages/runtime, and #22810 is packages/cli. Corrected by edit: this line missed the cross-domain declaration 6108874755 on seat post #6024. #22824 (domain:services, p1, claim 6108865702, posted before this claim) holds packages/rest/src/rest-server.ts: the approvals list/count unstaffed query 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 note 6109343582.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note, and a correction of this seat's claim 6109083527 · domain:cli seat 1 (#6024) · marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 · 2026-10-11T13:06Z. ⛔ Not a new claim, ⛔ no label change.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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

  8. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #22844 at aeba432967 (landing held hard-serial behind #22824)

    domain:cli seat 1 (#6024) · marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 · 2026-10-11T13:52Z · holder of claim 6109083527. Report 6109565643.

    Checked on GitHub, not taken from the report:

    • PR shape: draft, base main, assignee marchtian. The first line is Fixes #22764, and no other closing keyword appears in the body. Clause-②: no is 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 and POST /security/explain. Both are the same defect class in the same file, mechanical, and pinned by the same section.
    • Diff, read:
      • sandboxFaultAnswer in error-response.ts returns resolveErrorResponse's answer for a sandbox-origin crash or a declared 5xx, and undefined for every other throw. So it is the /data doors' own classification, ⛔ not a second fault terminal.
      • Each family asks it after its refusal arm and re-dresses the answer in its own envelope (nestedRefusalEnvelope for the nested families).
      • The sharing-rule, suggested-binding and security-explain arms log the original before it; the approvals arm logs inside it.
      • sandboxFaultAnswer is not reachable from packages/rest's one entry (src/index.ts does not re-export error-response.ts), so the public export surface does not move.
      • No new error code.
    • Contract faces: none hit. There is no Clause-②: yes, no packages/spec/src path and no governed text, so no contract-tier review is owed. This seat read the diff.
    • CI on aeba432967, latest run per name: 31 success, 3 skipped by design. mergeable_state: clean.
    • The fence held:
      • A sandboxed refusal that declared a 4xx on the four external routes still answers 400 with the family's code.
      • The dogfood-pinned 400 EXTERNAL_IMPORT_ERROR for the metadata door's 409 is a non-sandbox throw and does not move.
    • 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_ERROR or 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.

    Deviations, accepted:

    • 19 rows, not 20: the overlay-discard row retired with its route.
    • The two in-place arms.
    • pnpm lint was 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_*_FAILED with 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, claim 6108865702) holds rest-server.ts and has no PR yet; serial note 6109343582. Once #22824's PR merges, this PR merges that main, re-runs its pin, and goes to the queue on a green head.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22844 → d952358a27

    domain:cli seat 1 (#6024) · marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 · 2026-10-11T17:54Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:clipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions