Repository navigation
Description
Filed by the domain:cli execution seat (session_01DvvamiacK328idtBYJBxV3) out of the #18931 round (PR #18984), from that dev's out_of_scope_findings where it was marked 「to file」, class (a). ⛔ Filed bare: finding only; domain:* / type / priority are triage's.
Dedupe words: me/permissions · viewAllRecords · seedSuperUserRestrictedObjects · apiOperations · wildcard-only.
⚠️ Why this needs its own card rather than a line on #18931
Its home is closing. #18931 names this case as its own Option 2 and defers it ("Follow-up; not required") — but PR #18984 carries Fixes #18931, so the moment that PR lands the deferral has nowhere left to live. ⛔ A deferred option on a closed card is not a backlog entry.
The reading
#18931 was: seedSuperUserRestrictedObjects resolved each schema without the export slot and skipped every unrestricted one, while annotateEffectiveApiOperations resolves with it — two predicates for one question, disagreeing on exactly the population the export axis created. PR #18984 makes the seed apply annotate's own predicate.
That fix is scoped to the modifyAllRecords guard. A principal that reaches an object only via a wildcard allowRead grant sits outside that guard, so for it the channel still says nothing: the object gets no entry, the client falls back to default-allow, and it renders an Export button that the server answers 403 EXPORT_NOT_PERMITTED. Same silence, same consequence, different principal class.
PR #18984 pins this boundary deliberately rather than leaving it undefined — one of its three new cases is does not reach a viewAll-only principal (the seed guard is unchanged), asserting toBeUndefined. ⇒ the gap is pinned as current behaviour, so a successor changing it will have to change that assertion on purpose. ⛔ That pin is not a ruling that the behaviour is correct.
⛔ Why it was not fixed in #18984, and what the successor must settle FIRST
⛔ This is not a "widen the guard by one condition" chore. Widening the seed to viewAll-only principals would materialise an entry for a caller the fold does not pull true, flipping that client from default-allow to explicit deny — a behaviour change beyond #18931's scope, and a design call that card did not rule.
⇒ The successor's first act is to establish what answer the channel owes a viewAll-only principal, ⛔ not to copy #18984's patch. ⭐ Entry-presence is itself semantic here: "no entry at all" and "an entry with everything false" are different statements to a client, and only one of them is the truth about that principal.
Where to read it
packages/plugins/plugin-hono-server/src/current-user-endpoints.ts — ⭐ locate by symbol (seedSuperUserRestrictedObjects, annotateEffectiveApiOperations), ⛔ never by line number; PR #18984 moves both.
Generated by Claude Code
Activity
hotlong commentedon Sep 18, 2026
Ruling: batch #159 item 1 · letter A (
/auth/me/permissionsowes every object a principal can reach an explicit entry: the seed runs for a wildcard-onlyviewAllRecordsprincipal too —allowReadfolded true, the write bits false,apiOperationsannotated by the same predicate as #18931; PR #18984'stoBeUndefinedpin flips on purpose) · maintainer 「同意」 2026-09-18T11:42ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation A; the maintainer agreed. Facts (this card; triage 5728525942;current-user-endpoints.tsdocblocks read by the director):seedSuperUserRestrictedObjectsreturns early unlessobjects['*'].modifyAllRecords === true; its docblock guards the viewAll-only class because materialising a{allow*: false}entry would flip the client'scheck('edit')from 「undefined ⇒ default-allow」 to 「explicit false ⇒ deny」. The seed only touches objects with no explicit entry (if (objects[name]) continue), and for those a wildcard-only viewAll principal can only read — so 「explicit false」 for edit there is the truth, not a scope exceed. #18931 / PR #18984 fixed the modify-all class only and pinned this boundary as current behaviour, saying in the same breath that the pin is not a ruling. Consequence today: no entry ⇒ the client's default-allow path renders Export (and edit affordances) that the server refuses403.Ruling — A
seedSuperUserRestrictedObjectsalso seeds for a principal whose wildcard carriesviewAllRecords(read-only class): the seeded entry starts{allow*: false},foldWildcardSuperUserpullsallowReadtrue from the wildcard, the write bits stay false, andannotateEffectiveApiOperationsattachesapiOperationsby the predicate already shared with the modify-all class (skip only an unrestricted object whose export stays allowed).does not reach a viewAll-only principalin PR fix(plugin-hono-server): /auth/me/permissions speaks for an unrestricted object when the export axis withholds export #18984's suite is inverted deliberately, with the new case pinning: entry present,allowRead: true,allowEdit/Create/Delete: false,apiOperationswithoutexportwhen the export axis withholds it.Four-facet reading: ① one predicate for every principal class; ② a read-only role seeing a clickable Export in the console is the everyday case; ③ an AI client rendering from
/me/permissionsis told the truth; ④ same shape as #18931, no mechanism.Execution
needs-user-decision→pm:queue;domain:cli,priority:p1stay (the anchor rule may move it todomain:servicesif the seed lives in plugin-security — triage's re-lane).Clause-②: no(an existing published channel gains entries it should always have carried; no schema moves). Payload growth for viewAll principals is the same one-entry-per-object #18931 accepted.Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
Claim —
domain:cliexecution PM seat #6024Why this card is dispatchable now
Seat takeover audit
5747152616. The card is ruled: batch #159 item 1, letter A, maintainer 「同意」 2026-09-18T11:42Z, recorded by the director seat at5729478681. It left the decision box forpm:queuein that same stroke and is the highest-priority dispatchable card in the lane (priority:p1).⭐ The re-lane the director flagged as possible does NOT materialise, and this seat measured it rather than assuming. That ruling notes 「the anchor rule may move it to
domain:servicesif the seed lives in plugin-security」. Read atorigin/maine233db9:foldWildcardSuperUserandannotateEffectiveApiOperationsread out of the same file; the only other production hit is that file's own line 1090 call site. ⇒ every named symbol lands inplugin-hono-server, whichSKILL.md's domain table puts indomain:cli. The card stays in this lane, and ⛔ no label is changed on that basis.Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
os-dev-report
{ "issue": 18990, "status": "done", "branch": "claude/issue-18990-viewall-only-permissions-seed", "pr": "https://github.com/objectstack-ai/objectstack/pull/19242", "session": "session_01QCdUBjM47SxioST9z5Zwdf", "premise_still_valid": true, "premises": { "zone2_1_symbols_in_plugin_hono_server": "VERIFIED at branch base e233db9 — seedSuperUserRestrictedObjects, foldWildcardSuperUser and annotateEffectiveApiOperations all resolve out of packages/plugins/plugin-hono-server/src/current-user-endpoints.ts; sole production call site is in that same file. No re-lane.", "zone2_2a_early_return_guard": "VERIFIED in source — the pre-fix guard read `if (objects?.['*']?.modifyAllRecords !== true) return;`", "zone2_2b_only_untouched_objects": "VERIFIED in source — `if (!name || name === '*' || objects[name]) continue;`", "zone2_2c_client_check_flip": "NOT VERIFIABLE IN THIS CONTAINER — the claim is about the client's check(), which lives in the sibling objectui repo; /home/user/objectui is not checked out and packages/console ships only a built bundle. Does not change the shape of the fix: the ruling already holds the flip to be the desired outcome, and the server-side half IS verified (PermissionEvaluator treats viewAllRecords as the read bypass at permission-evaluator.ts:143 and :228, so folding allowRead true is exactly as broad as enforcement).", "zone2_3_export_predicate_shared": "VERIFIED — no new branch needed. Seed resolves resolveEffectiveApiMethods with userExportAllowed read off the wildcard allowExport; annotate resolves it with `acc.allowExport ?? wildExport`; a seeded entry carries no allowExport of its own and the fold adds none, so both resolve the same bit and both skip on the same `unrestricted && userExportAllowed`." }, "summary": "Widened seedSuperUserRestrictedObjects to admit the wildcard READ bypass (viewAllRecords OR modifyAllRecords) instead of the modify bit alone, through one module-local predicate that foldWildcardSuperUser now shares — so the seed structurally cannot materialise an entry for a principal the fold leaves entirely false, and no second predicate was added. A viewAll-only principal now gets an entry that starts all-false, has allowRead folded true, keeps the write bits false, and carries apiOperations from the predicate already shared with the modify-all class. A plain wildcard carrying neither bypass bit is still not seeded, pinned as its own case. Assignee arrived set to os-project-manager by the dispatching seat; not written by this run.", "clause_2": "no — CONFIRMED by structural measurement, not by a gate. check-widening-tells --declaration no exits 0 but reports all 3 files NOT MEASURED (no declared surface covers packages/plugins/**). Measured from the diff: zero `export` keywords added (wildcardGrantsSuperRead is module-local), no new key on the published payload (the objects map gains ENTRIES, not keys), no new registration, no new error code. check-clause2-carriers.mjs --pair 19242 :: exit 0, claim and PR body both read `no`.", "tests": "pnpm --filter @objectstack/plugin-hono-server test :: exit 0 (26 files, 311 passed, 1 todo) — re-run after the origin/main merge, same result. pnpm --filter @objectstack/plugin-hono-server typecheck :: exit 0 (tsc --noEmit x2 + check:test-typecheck, 0 files / 0 errors / 0 pinned signatures). pnpm --filter '@objectstack/plugin-hono-server^...' build :: exit 0 (dependency closure, local-verification item 1). ABLATION (reverse verification), run from the COMMITTED fix via scripts/ablation-replace.mjs: expected direction fixed BEFORE the run as 'the two new positive pins go red, the two absence-asserting pins cannot distinguish and stay green'. Observed exactly that — replacing the shared predicate with the old modifyAllRecords-only guard gave 'Tests 2 failed | 22 passed', failing 'seeds for a viewAll-only wildcard too' and 'reaches a viewAll-only principal: read folded true, writes explicitly false'. On-disk mutation proven by the script: anchor 1 -> 0, blob c29835679464 -> 3dd8cb1206e9; restore proven by blob == HEAD (c29835679464) and `git diff HEAD` empty. No dist involved: the test imports './current-user-endpoints.js' relative to the same src directory, so vitest resolves source, not a build. No ablation artefact remains.", "gates": [ { "cmd": "node scripts/check-adr-0087-registration.mjs --base origin/main", "exit": 0 }, { "cmd": "node scripts/check-adr-0087-registration.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-changeset-no-major.mjs --base origin/main", "exit": 0 }, { "cmd": "node scripts/check-changeset-no-major.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-ci-filter-parity.mjs", "exit": 0 }, { "cmd": "node scripts/check-closing-keyword-parity.mjs", "exit": 0 }, { "cmd": "node scripts/check-closing-keyword-parity.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-comment-mask-adoption.mjs", "exit": 0 }, { "cmd": "node scripts/check-comment-mask-adoption.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-comment-mask-corpus.mjs", "exit": 0 }, { "cmd": "node scripts/check-empty-changeset.mjs --base origin/main", "exit": 0 }, { "cmd": "node scripts/check-empty-changeset.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-keyed-text-bounds.mjs", "exit": 0 }, { "cmd": "node scripts/check-keyed-text-bounds.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-platform-object-tenancy-census.mjs", "exit": 0 }, { "cmd": "node scripts/check-platform-object-tenancy-census.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-plugin-teardown-shape.mjs", "exit": 0 }, { "cmd": "node scripts/check-plugin-teardown-shape.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-registry-log-declared.mjs", "exit": 0 }, { "cmd": "node scripts/check-registry-log-declared.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-rest-log-spy-declared.mjs", "exit": 0 }, { "cmd": "node scripts/check-rest-log-spy-declared.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-system-context-census.mjs", "exit": 0 }, { "cmd": "node scripts/check-system-context-census.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-tenant-audit-census.mjs", "exit": 0 }, { "cmd": "node scripts/check-tenant-audit-census.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/check-undeclared-dep-imports.mjs", "exit": 0 }, { "cmd": "node scripts/check-undeclared-dep-imports.mjs --self-test", "exit": 0 }, { "cmd": "node scripts/docs-audit/check-affected-docs.mjs", "exit": 0 }, { "cmd": "node scripts/docs-audit/check-drift-comment.mjs", "exit": 0 }, { "cmd": "node scripts/pm/release-rehearsal-clone.mjs --self-test", "exit": 0 }, { "cmd": "pnpm --filter @objectstack/spec run check:duration-unit-keys", "exit": 0 }, { "cmd": "pnpm check:changeset-gate-self-tests", "exit": 0 }, { "cmd": "pnpm check:cross-package-test-inputs", "exit": 0 }, { "cmd": "pnpm check:doc-authoring", "exit": 0 }, { "cmd": "pnpm check:driver-memory-census", "exit": 0 }, { "cmd": "pnpm check:dts-closure", "exit": 0 }, { "cmd": "pnpm check:dual-build-cjs-loads", "exit": 3 }, { "cmd": "pnpm check:engine-double-contract", "exit": 0 }, { "cmd": "pnpm check:lean-entry-closure", "exit": 3 }, { "cmd": "pnpm check:logger-receiver-detach", "exit": 0 }, { "cmd": "pnpm check:nul-bytes", "exit": 0 }, { "cmd": "pnpm check:objectql-double-limit", "exit": 0 }, { "cmd": "pnpm check:objectui-changeset", "exit": 0 }, { "cmd": "pnpm check:org-identifier", "exit": 0 }, { "cmd": "pnpm check:page-declaration-shape", "exit": 0 }, { "cmd": "pnpm check:pm-changeset-deadline-census", "exit": 0 }, { "cmd": "pnpm check:published-files", "exit": 0 }, { "cmd": "pnpm check:query-options-erasure", "exit": 0 }, { "cmd": "pnpm check:refd-timer-probe", "exit": 0 }, { "cmd": "pnpm check:route-envelope", "exit": 0 }, { "cmd": "pnpm check:slot-lookup", "exit": 0 }, { "cmd": "pnpm check:sourcemap-no-sources-content", "exit": 0 }, { "cmd": "pnpm check:test-source-alias", "exit": 0 }, { "cmd": "pnpm check:tier-file-adoption", "exit": 0 }, { "cmd": "pnpm check:type-check-coverage", "exit": 0 }, { "cmd": "pnpm check:type-check-debt", "exit": 3 }, { "cmd": "pnpm check:watch-hint-literal", "exit": 0 }, { "cmd": "pnpm check:where-matcher", "exit": 0 }, { "cmd": "pnpm lint", "exit": 0 } ], "gates_summary": "59 families derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (from the tool's own merge-base change set, not a hand-written path list), reconciled with --ran carrying every exit code: 56 run green, 3 NOT MEASURED, 0 UNRUN. Plus `pnpm lint` (the full repo union `eslint . --no-inline-config`, the lane's declared blind spot) :: exit 0 at final commit 0625da76a, outside the derived union. 13 of the 59 are checker-health-only self-tests and judge nothing about this diff.", "gates_not_measured": "3, each exit 3 = PREREQUISITE NOT MET, which is neither a pass nor a failure: check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt. All three read BUILT output across the whole workspace and this tree has only the plugin-hono-server dependency closure built; a full `turbo run build --filter=./packages/*` does not fit the 10-minute foreground cap and CI builds the workspace ahead of them. The diff cannot move them structurally (no package.json, no exports, no entry point touched). DECLARED NARROWING.", "line_budget": "n/a — no skills/** path in the diff, so no line ratchet is owed. Measured size: 128 changed lines (+110 / -18) across 3 files, under the 5000-line human-merge threshold.", "files_changed": [ "packages/plugins/plugin-hono-server/src/current-user-endpoints.ts", "packages/plugins/plugin-hono-server/src/effective-api-operations.test.ts", ".changeset/18990-viewall-only-permissions-seed.md" ], "file_surface": "NOT BREACHED. Declared surface packages/plugins/plugin-hono-server/src/ holds both code files. The only file outside it is the changeset this published package owes (patch), which the dispatch itself required. Nothing reaches packages/spec — resolveEffectiveApiMethods is imported, not edited.", "pin_flips": "2, both deliberate under the ruling and both named in the PR body. (1) 'does not reach a viewAll-only principal (the seed guard is unchanged)' — the PR #18984 pin the ruling names by its text. (2) 'does not seed for a viewAll-only wildcard (avoids flipping check() to explicit deny)' — the older #3391-era pin asserting the identical thing about the identical principal class. FLAGGING THE SECOND: the dispatch order named one pin, the diff flips two, because the ruling's subject is the principal class and not a single test name.", "deviations": [ "Deepened the shallow clone to run one gate: check-plugin-teardown-shape.mjs --self-test first exited 3 because its pinned fixture commit was unreachable. `git fetch --deepen 400` and `--deepen 3000 origin main` both failed to retrieve it; `git fetch origin 621a487607881c66b2899b7e3477115229a156b4` succeeded. Gate then :: exit 0. Side effect: origin/main advanced e233db9 -> 847e577 during that fetch (shared ref); all base-relative work was anchored to the recorded BASE e233db9, and 847e577 was then merged deliberately before the PR.", "Three gate families left NOT MEASURED as a declared narrowing — see gates_not_measured. Not a skip: each exits 3 by its own design so nothing reads it as either verdict.", "Zone 2 premise 2c could not be measured in this container (sibling objectui repo absent) — reported rather than asserted." ], "api_writes": "2 REST proxy writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR 19242, create with body in the same call — the body was never PATCHed), POST /repos/objectstack-ai/objectstack/issues/18990/comments (this report). Zero label writes, zero assignee writes, both left to the seat as instructed. Git pushes (not REST): 4, all to claude/issue-18990-viewall-only-permissions-seed. PR body read back after creation: byte-identical apart from the platform stripping the trailing newline; footer present exactly once in the session-URL form; the only closing-keyword pair in the body is `Fixes #18990`.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write. Every GitHub read and write went through the REST proxy with curl.", "open_questions": [], "out_of_scope_findings": [ "to file (class (a), dedupe words: check-plugin-teardown-shape · unshallow · pinned fixture commit · shallow clone self-test · 621a4876): scripts/check-plugin-teardown-shape.mjs --self-test prints a remedy that cannot work. On a shallow checkout it refuses with 'cannot read the positive control at 621a487607881c66b2899b7e3477115229a156b4 … Deepen the clone: git fetch --unshallow origin main'. That commit is NOT an ancestor of origin/main, so no depth of a main-only fetch retrieves it — it is the pre-squash commit of PR #10369, orphaned by the squash merge. Measured here: `git fetch --deepen 3000 origin main` left it absent; `git merge-base --is-ancestor 621a4876 origin/main` :: exit 1 with a control leg on the deepest reachable commit :: exit 0 (so the negative is real, not a shallow-clone artefact); `git fetch origin 621a4876…` retrieved it and the self-test then passed 48 cases. Cost: every agent on a shallow checkout copies the printed line, it changes nothing, and a runnable checker-health gate gets recorded as unrunnable. Fix is either the working remedy in the text or pinning the fixture to an in-history commit. Not fixed here: different defect class from this card, so it fails the bounded-in-place test.", "noted, not filed: the seed's docblock and its production call-site comment both described the guard as modify-all only; both were updated in this diff since they are the files under edit. Successor for the file: whoever next touches /me/permissions assembly in current-user-endpoints.ts." ] }Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
LANDED — PR #19242, squash
215840f4353c0e26697035f155710e70dbebb8dcdomain:cliseat #6024 · sessionsession_01QCdUBjM47SxioST9z5Zwdf· verified by CONTENT onorigin/main, ⛔ never from a merge event⛔ A merge event proves nothing about the branch — a successful queue merge emits
removed_from_merge_queueone second beforemerged, and that pair is equally compatible with a dequeue. So the landing is judged on the tree.Content on
origin/mainfunction wildcardGrantsSuperReaddeclaredwildcardGrantsSuperRead(objects)call sites (fold + seed)modifyAllRecords !== trueseeds for a viewAll-only wildcard toodoes not seed for a wildcard carrying neither bypass bit.changeset/18990-viewall-only-permissions-seed.md⭐ The
0is load-bearing and is a reading rather than an artefact: the five probes above run over the same two files through the same matcher, and four of them return non-zero, so the instrument is demonstrably alive where it returns nothing.Ancestry — with a negative control that could actually have failed
compareAPI, ⛔ not a localmerge-base: this checkout is shallow and a shallow clone answers false non-ancestor.⇒ the negative control discriminated. Had the queue fast-forwarded instead of squashing, that row would read
behind, and it does not.⛔
merge_commit_shawas trusted here only because this PR is MERGED — on an OPEN PR it is a candidate and this seat has measured that 10×. ⛔ The landing commit's own timestamp is the enqueue time, so it ⛔ never dates a landing.What landed
The ruling batch #159 item 1 letter A, implemented as one module-local predicate both
foldWildcardSuperUserandseedSuperUserRestrictedObjectsshare — so the seed structurally cannot materialise an entry for a principal the fold leaves false, and no second predicate was added, which was the exact defect #18931 was about./auth/me/permissionsnow answers a wildcard-onlyviewAllRecordsprincipal instead of staying silent about every object it can reach.The artifact-on-disk e2e assertions:
ci.yml:663setsOS_TEST_TIERS: queue, which keeps*.e2e.test.*off the PR run. ⇒ this landing rests on the unit tier plus the ablation the dev ran, ⛔ not on the nightly tier.Filed from this round and ⛔ deliberately not ridden: #19243 (
check-plugin-teardown-shape --self-testprints an unshallow remedy that cannot retrieve its own orphaned fixture commit).Readings taken and posted 2026-09-20T09:03Z.
Generated by Claude Code
fix(hono-server): seed /me/permissions for a viewAll-only wildcard pr…
fix(core): effective-map entries reached through a super-user `*` car…
fix(core): apiOperations offers only what the REST door serves — noth…