Skip to content

ObjectPermissionSchema's retired allowRestore/allowPurge: only literal false parses (not a truthy/falsy split), and no post-parse guard can ever see either key #17425

Description

@baozhoutao

Split off #16277 by the dispatch brief — triage judged this measurement "worth its own card" but did not re-run it, so did not file it. I re-ran it and confirmed, with a more precise characterization than the original report.

Original claim (from #16277)

Measured on the installed @objectstack/spec 17.3.0 (ObjectPermissionSchema.safeParse): only true is refused. allowRestore: false / allowPurge: false parse successfully, and the parsed output carries neither key — the retired-default residue tolerance of #12840. Consumers writing a guard against these bits therefore need === true, not a presence or truthiness check.

What I measured (origin/main @ cef399be82's base, packages/spec/src/security/permission.zod.ts, ObjectPermissionSchema.safeParse, run via pnpm --filter @objectstack/spec exec tsx against a minimal valid base object)

allowRestore=false        success=true   carriesKey=false   (dropped — #12840 residue tolerance)
allowRestore=true         success=false  code=invalid_type  ("expected": "never", ADR-0049 removal message)
allowRestore="true"       success=false  code=invalid_type  (same — NOT boolean-specific)
allowRestore="false"      success=false  code=invalid_type  (same — a string "false" is ALSO refused)
allowRestore=0            success=false  code=invalid_type  (same — a falsy non-boolean is ALSO refused)
allowRestore=1            success=false  code=invalid_type  (same)
allowRestore=null         success=false  code=invalid_type  (same)
key entirely absent       success=true   carriesKey=false   (never added)

allowPurge behaves identically (spot-checked true/false).

The correction to the original framing

The original report says "only true is refused" — that undersells it. The schema accepts exactly one value, the boolean literal false; every other value of any type is refused with the same ADR-0049 removal message (code: 'invalid_type', expected: 'never'), not just true. A string "false", the number 0, or null are refused exactly like true is — this is not a truthy/falsy check, it is a z.literal(false)-shaped tombstone gate (see retiredKey() in packages/spec/src/shared/retired-key.ts for the general shape).

The consumer-facing asymmetry, stated precisely

A successfully parsed ObjectPermissionSchema object can never carry allowRestore or allowPurge at all: the only value that survives parsing (false) is stripped from the output, and every other value throws before a parsed object exists. So:

  • Post-parse, permissions.allowRestore is always undefined — a presence check ('allowRestore' in permissions) or truthiness check (if (permissions.allowRestore)) against parsed/validated output is not merely imprecise, it is dead code: the condition can never be true, on any input that survived validation.
  • The original report's suggested remedy ("write === true, not a presence/truthiness check") does not fix this for parsed output either — permissions.allowRestore === true is also always false post-parse, because a raw true never survives safeParse/parse in the first place (it throws).
  • The distinction only has a live consumer on pre-parse / raw author input (e.g. a linter or migration tool reading objectstack.json source before validation): there, false (legacy no-op) and true/other (hard ADR-0049 violation) are different facts an author-facing tool should say different things about, and a presence/truthiness check on raw input conflates them.

Ask

Whichever key's doc/prompt surface discusses this (ObjectPermissionSchema's .describe() text, the retired-key migration guidance, or downstream consumer docs) should state the parse-time behavior precisely: only false parses, and is stripped; every other value refuses at parse with the ADR-0049 message; consumers of parsed/validated data will never see either key, so no post-parse guard is meaningful — pre-parse/raw-source tooling is the only place a false-vs-other distinction is observable.

Provenance

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions