Skip to content

Commit 7c2b636

Browse files
committed
docs(spec,types,rest,runtime): state who sets userMessage now that a platform refusal does
Six comments said platform and driver code never set a thrown userMessage. The packaged-permission-set lock's refusals now do, with static guidance and no host state. Each comment now states the invariant that holds: only a producer authoring end-user text with no host state sets it (an application hook, or a platform refusal carrying static guidance); platform and driver diagnostics never do. Comment-only: each file's comment-free TypeScript print is byte-identical before and after, and the ApiErrorSchema.userMessage describe() text is unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011K3zqE8Pv1Evw5hc8tZCnN
1 parent b8da8b3 commit 7c2b636

6 files changed

Lines changed: 33 additions & 18 deletions

File tree

‎packages/rest/src/rest-server.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12148,9 +12148,11 @@ export class RestServer {
1214812148
// derivation and one reason with two copies is this lane's own
1214912149
// recurring defect). `userMessage` has NO invariant left for a
1215012150
// caller to re-derive. `declaredUserMessage` already decided
12151-
// PRESENCE — the field exists on an error only because an
12152-
// author deliberately wrote caller-facing text onto it, and
12153-
// platform and driver code never set it — and
12151+
// PRESENCE — the field exists on an error only because its
12152+
// producer deliberately wrote end-user text with no host state
12153+
// onto it (an application hook, or a platform refusal carrying
12154+
// static guidance such as the packaged-permission-set lock's;
12155+
// platform and driver diagnostics never set it) — and
1215412156
// `truncateClientMessage` already applied #5423's bound to the
1215512157
// value. Reading `refusal.body.userMessage` IS the rule; there
1215612158
// is no second function to run it through, and running one

‎packages/runtime/src/http-dispatcher.permission-denied-user-message.test.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
* `object` is derived from the ROUTE, never from `details.object` (which, on a
3636
* cascade delete, names a CHILD the caller never addressed). `§3` drives that
3737
* exact fixture **with a mark present**, so the repair cannot be read as
38-
* loosening the withhold: the marked channel is authored end-user text,
39-
* carried as a declared top-level sibling of `code`/`message`, and platform and
40-
* driver code never set it.
38+
* loosening the withhold: the marked channel is authored end-user text with
39+
* no host state (an application hook's, or a platform refusal's static
40+
* guidance such as the packaged-permission-set lock's), carried as a declared
41+
* top-level sibling of `code`/`message`; platform and driver diagnostics never
42+
* set it.
4143
*
4244
* That same ruling is also why the change is owed: it makes REST's shape the
4345
* contract for BOTH transports, and REST's shape has carried the mark on this

‎packages/runtime/src/http-dispatcher.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2828,7 +2828,10 @@ export class HttpDispatcher {
28282828
// SERVER-side diagnostics attached by `plugin-security` and are
28292829
// still dropped above. `userMessage` is the opposite by
28302830
// construction: it exists only because a producer wrote text
2831-
// FOR the caller, platform and driver code never set it, and it
2831+
// FOR the caller with no host state in it (an application
2832+
// hook, or a platform refusal carrying static guidance such as
2833+
// the packaged-permission-set lock's); platform and driver
2834+
// diagnostics never set it, and it
28322835
// lands as a declared top-level sibling of `code`/`message`
28332836
// (`ApiErrorSchema.userMessage`), never inside `details`.
28342837
// The mark never moves the status or the `code` (commit 79c46da90).

‎packages/runtime/src/sandbox/quickjs-runner.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,9 +1275,12 @@ function safeJsonStringify(v: unknown): string {
12751275
* writes `const e = new Error(msg); e.userMessage = msg; throw e`, and the
12761276
* text must survive the VM flattening the throw to a string, or the marking
12771277
* dies exactly where its primary producers live. Crossing INTO the VM is safe
1278-
* for the same reason `code` is: the value is author-written user-facing text
1279-
* by construction (platform and driver code never sets the field), so it
1280-
* carries no host state a sandboxed body could exfiltrate — and it keeps the
1278+
* for the same reason `code` is: the value is text its producer authored for
1279+
* the end user (an application hook's, or a platform refusal's static
1280+
* guidance such as the packaged-permission-set lock's, which names no set,
1281+
* package, id or path), and platform and driver diagnostics never set the
1282+
* field, so it carries no host state a sandboxed body could exfiltrate — and
1283+
* it keeps the
12811284
* established property that a body which catches, inspects and re-throws a
12821285
* host error does not lose the structured payload.
12831286
*/

‎packages/spec/src/api/contract.zod.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,12 @@ export const ApiErrorSchema = lazySchema(() => z.object({
8080
* and the marked text are one value, so a boundary that rewraps or
8181
* substitutes `message` (sanitisation, truncation, the sandbox debug
8282
* wrapper) can never accidentally promote platform prose into the marked
83-
* channel. Platform/driver code never sets it. Same audience-split
84-
* precedent as `developerMessage` on the DELETE_RESTRICTED envelope
85-
* (#7307), pointed the other way.
83+
* channel. It is set only by a producer that authors end-user text with
84+
* no host state: an application hook, or a platform refusal that carries
85+
* static guidance (the packaged-permission-set lock's refusals in
86+
* `@objectstack/plugin-security`). Platform and driver diagnostics never
87+
* set it. Same audience-split precedent as `developerMessage` on the
88+
* DELETE_RESTRICTED envelope (#7307), pointed the other way.
8689
*
8790
* It never replaces `message` — the diagnostic channel keeps its own wording
8891
* for logs and developers.

‎packages/types/src/data-error-classification.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -672,11 +672,13 @@ export function mapDataError(error: any, object?: string): { status: number; bod
672672
* Why riding it across the FAULT terminals is safe rather than a #5437/#7543
673673
* regression: those disciplines withhold text the producer never addressed to
674674
* the caller — driver prose, a crash's `TypeError: …`. `userMessage` is the
675-
* opposite by construction: it exists on an error only because an author
676-
* deliberately wrote user-facing text onto it (`declaredUserMessage` answers
677-
* `undefined` for everything else — platform and driver code never sets the
678-
* field), so carrying it discloses nothing that was not authored for exactly
679-
* this audience. A genuine crash carries no marking and its envelope is
675+
* opposite by construction: it exists on an error only because its producer
676+
* deliberately wrote end-user text with no host state onto it — an
677+
* application hook, or a platform refusal carrying static guidance such as
678+
* the packaged-permission-set lock's (`declaredUserMessage` answers
679+
* `undefined` for everything else; platform and driver diagnostics never set
680+
* the field) — so carrying it discloses nothing that was not authored for
681+
* exactly this audience. A genuine crash carries no marking and its envelope is
680682
* byte-identical to before. The one thing the marking never does is move the
681683
* STATUS or the `code` — a marked crash is still the sanitised 500.
682684
*/

0 commit comments

Comments
 (0)