You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Decision] A sandboxed CRASH carrying a declared code is answered by the arm's status and the QuickJS wrapper prose, where the unwrap door answers the same crash with a sanitised 500 #15071
Filed by the domain:cli seat out of #14704 (PR #15065). ⭐ This is the follow-up card triage reserved by name, in its own words: "⛔ Do not decide the crash question. If a sandboxed crash (isScriptFaultMessage, #7543) reaches a code-gated arm, leave today's behaviour exactly as it is, implement the business-message read only, and name the site and the divergence in the report … ⛔ do not ask mid-run and ⛔ do not pick one."
The implementing dev did exactly that and recorded no recommendation, deliberately. So does this card: the analysis below is input, ⛔ not a lean.
The site
structuredCodeAnswer, and the PERMISSION_DENIED arm just below the consult — both reached from mapDataError in packages/rest/src/error-response.ts. Locate by symbol; line numbers in that file rot within the day.
The divergence, in wire shapes
the producer
which door answers
what the caller gets
sandboxed body crashes while carrying a declared code
the code-gated arm
that arm's status + the QuickJS wrapper prose — e.g. 409, code: DELETE_RESTRICTED, error: "hook 'guard' threw: TypeError: x is not a function"
sandboxed body crashes carrying no declared code
classifyDataError's unwrap door
the sanitised fault terminal — 500, UNCLASSIFIED_FAULT (#7543)
⇒ Two answers to the same event, chosen by whether the crashing body happened to declare a code. One leaks a stack-shaped internal sentence to an end user at a declared business status; the other is the sanitised terminal that exists precisely to stop that.
Behaviour today is unchanged by #14704, and the fence is mechanical
⭐ Worth stating because it is what makes deciding this later cheap: sandboxBusinessMessagedeclines a crash by contract, so the new armSentence(error) = sandboxBusinessMessage(error) ?? error?.message falls back to error.message and the answer does not move a byte. The fence holds by construction, not by anyone's intention.
And it is pinned as an ACCEPTED DIVERGENCE in error-response-sandbox-arm-message.test.ts §4, with a positive control (the same crash carrying no declared code does reach the sanitised 500). ⇒ Whatever this card decides, the change will be a visible edit to a named case, never a drift.
The options
A — status quo. An arm gated on a declared code keeps answering a crash with its own status and the wrapper prose. What PR #15065 ships, unchanged.
B — a crash outranks the arm. A sandboxed crash reaches the unwrap door's terminal (sanitised 500) whatever code it declares, matching the rule the unwrap door already applies one branch down. Consistent, and it removes the leak — at the cost of an author who declared DELETE_RESTRICTED and then crashed no longer seeing their declared status.
C — split the two decisions. The arm's status, with the prose withheld: fault classification and status declaration answered separately. The most precise and the only one that adds a mechanism.
Why it is a decision and not a fix
It is fault classification, not message sourcing. The question underneath is whether a declared code is a statement about this response or about this failure mode — and the two doors currently answer it differently on purpose-by-accident. That is a ruling about what the wire means, so it belongs in the maintainer's inbox rather than in a dev's judgement.
⚠️ Whoever takes it should note the population: a declared code plus a crash is the ordinary shape of an app author's hook that validates, declares a refusal, and then has a bug on a later line. It is not exotic.
Filed by the
domain:cliseat out of #14704 (PR #15065). ⭐ This is the follow-up card triage reserved by name, in its own words: "⛔ Do not decide the crash question. If a sandboxed crash (isScriptFaultMessage, #7543) reaches a code-gated arm, leave today's behaviour exactly as it is, implement the business-message read only, and name the site and the divergence in the report … ⛔ do not ask mid-run and ⛔ do not pick one."The implementing dev did exactly that and recorded no recommendation, deliberately. So does this card: the analysis below is input, ⛔ not a lean.
The site
structuredCodeAnswer, and thePERMISSION_DENIEDarm just below the consult — both reached frommapDataErrorinpackages/rest/src/error-response.ts. Locate by symbol; line numbers in that file rot within the day.The divergence, in wire shapes
409,code: DELETE_RESTRICTED,error: "hook 'guard' threw: TypeError: x is not a function"classifyDataError's unwrap door500,UNCLASSIFIED_FAULT(#7543)⇒ Two answers to the same event, chosen by whether the crashing body happened to declare a code. One leaks a stack-shaped internal sentence to an end user at a declared business status; the other is the sanitised terminal that exists precisely to stop that.
Behaviour today is unchanged by #14704, and the fence is mechanical
⭐ Worth stating because it is what makes deciding this later cheap:
sandboxBusinessMessagedeclines a crash by contract, so the newarmSentence(error) = sandboxBusinessMessage(error) ?? error?.messagefalls back toerror.messageand the answer does not move a byte. The fence holds by construction, not by anyone's intention.And it is pinned as an ACCEPTED DIVERGENCE in
error-response-sandbox-arm-message.test.ts§4, with a positive control (the same crash carrying no declared code does reach the sanitised 500). ⇒ Whatever this card decides, the change will be a visible edit to a named case, never a drift.The options
A — status quo. An arm gated on a declared code keeps answering a crash with its own status and the wrapper prose. What PR #15065 ships, unchanged.
B — a crash outranks the arm. A sandboxed crash reaches the unwrap door's terminal (sanitised 500) whatever code it declares, matching the rule the unwrap door already applies one branch down. Consistent, and it removes the leak — at the cost of an author who declared
DELETE_RESTRICTEDand then crashed no longer seeing their declared status.C — split the two decisions. The arm's status, with the prose withheld: fault classification and status declaration answered separately. The most precise and the only one that adds a mechanism.
Why it is a decision and not a fix
It is fault classification, not message sourcing. The question underneath is whether a declared code is a statement about this response or about this failure mode — and the two doors currently answer it differently on purpose-by-accident. That is a ruling about what the wire means, so it belongs in the maintainer's inbox rather than in a dev's judgement.
Refs
classifyDataError's structured-code arms ship the QuickJS debug wrapper aserrorfor a sandboxed producer — the mirror of #11588, on the door #11588 did not touch #14704 / PR fix(rest): the by-id /data door stops shipping the QuickJS wrapper out of a declared-code arm #15065 — the card this is reserved out of; its §4 pin is the ready-made harness.TypeError: not a functionleaks in the 400 envelope for{"title": 12345}— nocode, nofields[]#7543 — the sanitised fault terminal the unwrap door applies.hook '<name>' threw: Error: …reaches the client on every write route that exits abovemapDataError's unwrap (batch, createMany, updateMany, deleteMany, clone, analytics) #11588 — the same message-source rule, established one door over.handleRouteErroranswer the engine'sDUPLICATE_RECORDenvelope fromresolveErrorResponse's.statuspassthrough —fieldandobjectdropped,mapDataError's structured 409 arms never consulted #14541 / PR fix(rest): consult the bespoke structured arms before the declared-status passthrough, so both error doors answer one refusal with one body #14713 — the sharedstructuredCodeAnswer.mapDataError'sDUPLICATE_RECORDarm keep itsname === 'DuplicateRecordError'envelope gate, or key on the code alone like its relay siblings? #15070 — the other question escalated from rest:classifyDataError's structured-code arms ship the QuickJS debug wrapper aserrorfor a sandboxed producer — the mirror of #11588, on the door #11588 did not touch #14704; ⛔ independent of this one.