Repository navigation
tests(attachments): pin unscoped-multi-delete clause C3 end to end, and correct the ledger that still calls it broken - #11326
Merged
os-zhuang merged 1 commit intoAug 23, 2026
Conversation
…hat still calls it broken QA run #9401 listed `attachments-storage.attach-requires-parent-edit` clause C3 ("an unscoped multi-delete is refused outright", #4757) as unpinned. #9724 could not pin it: the refusal was unreachable through the wired engine (#9719). That was fixed by PR #9797 and extended to the update verb by #9974, so the clause is now pinnable AS WRITTEN. Re-verified on the merged ref, then measured a detection gap the earlier pin could not see: the existing dogfood block splits its rows entitled/not, so the per-row gate answers with the same code and the block stays 5/5 GREEN with `dispatchUnscopedMultiWrite` ablated from both registrations. - second dogfood block seeds a caller entitled to EVERY matched row -- the only shape that separates "refused outright" from "every row was denied" -- plus the empty-table (zero-match) limb, with both sides held by a scoped sweep - existing block: assert status + the #4757 message head, so it can tell the two refusals apart; `where: {}` re-documented as the per-row rule it actually is - checklist: clause 3 flips to pinned; oracle `api` -> `test` and step 6 rewritten, because the REST lane cannot express the shape (#3897) and a schema rejection must not be scored as the refusal Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
os-zhuang
marked this pull request as ready for review
August 23, 2026 11:52
os-zhuang
deleted the
claude/issue-9483-attachments-authz-remaining-clauses
branch
August 23, 2026 12:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #9483
QA run #9401 listed
attachments-storage.attach-requires-parent-editclause C3 —"an unscoped multi-delete (no id AND no where) is refused outright (#4757)" — as
unpinned. #9724 correctly declined to pin it: measured end to end, the refusal did
not fire, and pinning the behaviour of the day would have turned the eventual fix
red. That product gap (#9719) was closed by PR #9797 and widened to the update verb
by #9974.
So the first deliverable here was re-verification, not test-authoring. Walking
the card's clause list against the merged ref:
maindownload-authz-both-sidesC3attachments-public-read-acl.dogfood.test.ts, ref accuratedownload-authz-both-sidesC4read-inherits-parent-rlsC1 + C3attach-requires-parent-editC3Nothing #9724 landed is re-pinned.
The ledger was wrong in the dangerous direction
automated.refandknownGapsstill read "⛔ CLAUSE 3 IS NOT PINNED … the #4757'refused outright' rule does NOT fire through ObjectQL.delete". That has been false
since 2026-08-19. The next Tier-1 runner would have been told that a shipped, working
safety guard is broken.
The measured detection gap — why a new pin was needed at all
"Refused outright" and "the per-row gate refused one of the rows" answer with the
same code, so they are indistinguishable on a fixture whose rows split
entitled/not — which is exactly the fixture the landed block uses.
Measured, not argued: with
dispatchUnscopedMultiWriteremoved from bothsys_attachmentregistrations,service-storagerebuilt and the ablation provenlive in
dist/(ablation-dist-preflight.mjs→ "marker absent from all 6 builtfiles"), the existing block stays 5/5 GREEN. It cannot see the clause it is
named for.
The new block seeds the one fixture that separates them — a caller who uploaded
every matched row, whom the per-row gate would wave through — plus the
empty-table (zero-match) limb, which no handler-only fix could ever reach.
Ablation (both legs rebuilt, both dist-preflighted)
Same mutation, against the extended suite: 3 failed | 7 passed, exactly as
predicted, each red for its predicted reason — a matching count alone is not a pass.
expected Error: Cannot delete attachment … to match object(the per-row gate answered)promise resolved "2" instead of rejecting— #9719's measurement reproducedpromise resolved "+0" instead of rejectingRestore leg: "marker present in 2 built files"; tree clean,
srccount back to 2.Also corrected in the item
oracleapi→test, and step 6 rewritten. The step prescribed a REST probethe item's own
knownGapssays the HTTP lane cannot express(
DeleteManyDataRequestSchemarequires an ids list,deleteManyData把请求体的options展开在where之后 —— body 可覆盖 id 谓词,把"删这几条"放大成"删调用者可见的全部" #3897). A runner following itwould score a schema rejection as the refusal — green for the wrong reason.
where: {}re-documented. It reaches the same verdict by a different rule;the whole-operation dispatch is deliberately scoped to a delete with no
whereatall. The case now asserts the per-row message, so that boundary goes red rather
than silently agreeing if
installAttachmentAccessHooksdoes not authorize an UNSCOPED multi-delete: no id + nowherereads as "nothing to authorize" anddeleteManyruns over the whole table #4757's refusal ever widens.MULTI_DELETE_AUTH_LIMIT→MULTI_WRITE_AUTH_LIMIT.Verification — union at
e3da4cda(the final commit)Test Files 1 passed (1)/Tests 10 passed (10).Gates green, each read from its own verdict line:
check:platform-checklist(15areas, 205 items) ·
check:nul-bytes(6349 files) ·check:engine-double-contract·check:where-matcher·check:test-source-alias·check:cross-package-test-inputs·check:type-check-coverage·check:query-options-erasure·check:doc-authoring·check:doc-formula-expressions·check:published-files·check:slot-lookup·check:type-source-resolution·check-ci-filter-parity·check-plugin-teardown-shape·check-affected-docs· speccheck:empty-state/check:liveness/check:strictness-ledger/check:variant-docs·@objectstack/dogfood typecheck.check:type-check-debt --re-measurerefused first on an unbuilt closure — recordedas NOT MEASURED, then the closure was built exactly as
lint.ymldoes and it re-rangreen: "33 ledger entr(ies) re-measured in 233.9s, 1897 raw tsc error(s) total, none
above its recorded number." No baseline moved.
ESLint narrowed to the diff and the narrowing is measured, not assumed: 2 files
submitted,
--format jsonreports the.tsfile linted with 0 errors / 0 warningsand the
.json"ignored because no matching configuration was supplied" — so eslint'sown config puts it outside the linted population. Type-aware linting is not enabled
(
eslint.config.mjsstates noparserOptions.project, no typed rules), so this diffcannot move the verdict on any untouched file.
No changeset:
@objectstack/dogfoodisprivate: trueand the diff is tests pluschecklist prose, so this releases nothing —
skip-changesetapplied, per this repo'sroute 2 (an empty-frontmatter changeset is explicitly the wrong route here).
Generated by Claude Code