Uh oh!
There was an error while loading. Please reload this page.
fix(lint): stop the readonlyWhen hints ruling out the working remedy and offering a useless one - #14202
Conversation
…and offering a useless one Message text only across three carriers; rule ids, severities and match sets untouched. The flow hint recommended runAs:'system'. The conditional strip has no isSystem guard at all, so that is a privilege widening for no behaviour change (LOCK 2 pins it). The hook hint and the hook-bodies.mdx bullet asserted readonlyWhen strips a beforeUpdate-derived value -- the behaviour #9107 removed -- thereby ruling out the one remedy that works. All three now name the two measured remedies and refuse elevation, following the shape action-api-update-readonly-when-field already ships. The static-readonly hints that recommend elevation are deliberately unchanged; a new pin holds the two apart. Fixes#13832 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 127f4bf4abed84c8f00fdfbb2a19bc9f3c634419 && git checkout 127f4bf4abed84c8f00fdfbb2a19bc9f3c634419
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 07cced5ab97a2ec34be6c18e15b8956507885005 3bebbecad7cd50ae67545cf4ce1faa3c8dbee883 && git checkout -B drift-repro 07cced5ab97a2ec34be6c18e15b8956507885005 && git merge --no-ff 3bebbecad7cd50ae67545cf4ce1faa3c8dbee883
node scripts/docs-audit/affected-docs.mjs --json 07cced5ab97a2ec34be6c18e15b8956507885005 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#13832
Message text only, across the three carriers the card and its follow-up name. Rule ids, severities and match sets are untouched — no finding changes shape, appears, or disappears. But the hint is the whole product of an advisory rule (neither
readonlyWhenfinding blocks a build), so the sentence is the entire thing the author acts on, and two of these sentences were measured false against the engine.What was wrong, and in which direction
The defect ran in both directions at once — one hint recommended a remedy that does nothing, and two carriers ruled out the remedy that works.
The false remedy —
flow-update-readonly-when-fieldsaid:It does not.
stripReadonlyWhenFieldsruns on the update path with noisSystemguard at all, unlike the staticreadonlystrip immediately below it, which really is skipped for system callers.packages/objectql/src/engine.tssays so at the call site, in the #9107 note: "isSystemis still NOT an exemption here, unlike the static strip below." So the advice bought the author an elevated run identity, a re-run, the same missing column — andrunAs:'system'in the tree with no compensating behaviour. That is what the triage grading called decisive: advice to widen a write's privileges for no effect.The ruled-out remedy — the
hook-api-update-readonly-when-fieldhint and the matchinghook-bodies.mdxbullet both asserted:That is the behaviour #9107 removed. The conditional strip now judges the caller's entry snapshot, so a value a
beforeUpdatehook derives is not caller-supplied and lands even on a locked record.Premise re-verification — two carriers had already half-moved
Re-checked against
origin/mainbefore editing, and reported here because it changes what the card asked for. PR #14044 ("Stop lowering hook handlers that callctx.api.sudo()into bodies that cannot run it") already removed the elevation half from two of the three carriers:validate-readonly-hook-writes.tshook-bodies.mdxbulletvalidate-readonly-flow-writes.tsSo the card's title claim ("the hints on the hook and flow rules recommend elevation") is now true only of the flow rule. The defect is real on all three, but the hook and docs halves that survived are the derived-value falsehood, not the elevation one. Nothing here re-litigates #14044 — its correction stands and is preserved verbatim, including its stronger, separate reason that
sudo()is aTypeErrorfrom a sandboxed body.The three carriers, before to after
1.
packages/lint/src/validate-readonly-hook-writes.ts—hook-api-update-readonly-when-fieldsudo()is unreachable from a body (Stop lowering hook handlers that callctx.api.sudo()into bodies that cannot run it #14044's reason, kept), and a system context does not waive the conditional lock in any case. The second reason is what keeps the refusal correct if the first is ever fixed.2.
packages/lint/src/validate-readonly-flow-writes.ts—flow-update-readonly-when-field3.
content/docs/automation/hook-bodies.mdx— the "Writing areadonlyfield" bulletreadonlyWhenalso strips abeforeUpdate-derived value, so the own-hook stamp is not a workaround for it …"Deliberately not flattened
The static-
readonlyhints and docs rows that recommend elevation are unchanged, because for that strip elevation genuinely is the intended channel. The flow rule'sreadonlyhint still saysrunAs:'system'; the docs' action paragraph still says areadonlywrite lands in an elevated action body. The two disagree for a reason, and a new pin in the flow test now holds them apart explicitly, so a future text sweep cannot quietly align them.One in-file comment correction rides along, named here rather than left silent:
validate-readonly-flow-writes.ts's header asserted that arunAs:'system'run makes "the engine skip the strip entirely" — the same false belief that produced the hint, and it would now contradict the corrected hint in its own file. It is narrowed to the static strip, with the engine evidence cited. No match-set change: the rule still skipsrunAs:'system'flows entirely. That skip is genuinely wider than the conditional lock warrants — a system flow writing areadonlyWhenfield is still stripped on a locked record and goes unflagged — which is a behaviour question the triage fenced out of this card; filed as #14201 and noted in the comment.Verification
Gate families derived from the actual diff via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(the script reads its own change set; the first derivation reported a STALE TREE, soorigin/mainwas merged and it was re-derived on the merged tree). Exit codes captured by redirect before any pipe.pnpm --filter @objectstack/lint exec vitest run src/validate-readonly-hook-writes.test.ts src/validate-readonly-flow-writes.test.ts src/validate-readonly-action-writes.test.ts→ 3 files, 79 tests passed.Ablation (the pins are new, so they had to be shown capable of failing): the old hint text was restored on both rule files, the mutation confirmed on disk by occurrence count in both directions (injected text present, replaced text absent), the suite re-run, and the tree restored
HEAD-pinned. Result: exactly the 2 new pins failed, 45 other tests unaffected; restore proven byte-identical toHEAD(blob hashes matched,git diff HEADempty). Direction as predicted: RED.Green on the merged tree:
check:doc-authoring,check:doc-anchors,check:docs-single-h1,check:docs-redirects,check:docs-audit-scope,check:corpus-claim-drift,check:cross-package-test-inputs,check:changeset-gate-self-tests,check:engine-double-contract,check:objectql-double-limit,check:published-files,check:query-options-erasure,check:role-word,spec check:docs,spec check:skill-examples,lint check:doc-formula-expressions,lint check:doc-security-posture, and the rest of the derived family.Recorded as NOT MEASURED, not as passes — each exited on a missing prerequisite (exit 3) rather than reaching a verdict:
check:dual-build-cjs-loads(needs a fullpnpm build),check-test-completeness.mjs(needs a savedturbo run testlog; no local reading exists), andscripts/pm/check-half-states.mjs(a GitHub-querying patrol gate; it hung on network locally). Three others —lint check:doc-formula-expressions,lint check:doc-security-postureandspec check:skill-examples— first returned prerequisite-not-met, and are reported green only after building@objectstack/lintand@objectstack/client-reactand re-running them.Generated by Claude Code
Generated by Claude Code