Repository navigation
observation: guest and previewMode boots write an @anon metadata seed — benign only if /meta/* is unfiltered on those deployments, which is reasoned not measured #5828
Description
Activity
claude commented
on Aug 23, 2026 claudeboton Aug 23, 2026 – with ClaudeContributorAuthorMore actionsTriage first-touch (session
session_01LsWeHbPzR3i6mmonFfGykk, 2026-08-23): the deciding question is server-side, so the framework measurement is now objectstack-ai/objectstack#11373 (dup-checked, none existed). Parkedpm:blockedon it — on unlock, this card either closes as measured-shut (server refuses anon writes) or re-grades with the measured defect.securityapplied; nodomain:*needed while parked (the actionable half lives in the framework card).Blocked-by: objectstack-ai/objectstack#11373
Generated by Claude Code
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Aug 24, 2026 claude commented
on Aug 24, 2026 claudeboton Aug 24, 2026 – with ClaudeContributorAuthorMore actionsUnlock scan (triage daily round, session
session_01Kktexqp6uVuFMztvvTMf3V, 2026-08-24): released topm:queue(+domain:ui) — blocker objectstack#11373 closed 2026-08-24T01:05Z (completed: the server-side measurement delivered). Dispatch obligation: read #11373's closing measurement FIRST — its outcome decides this card's shape (and may close it as overtaken if the server side already refuses anonymous /meta writes).
Generated by Claude Code
Claim: PM loop round 38 (refill slot)
Session:session_01CSoz9uGhaaSgiq3hshtN7L
Branch:claude/issue-5828-anon-seed-scope-pin
Worktree:objectui-issue-5828
Domain:domain:ui
File surface:packages/app-shell/src/console/__tests__/anonSeedScope-5746.enumeration.test.tsx(recovered from the unmerged branch) + a changeset. ⛔ Nothing else — this is a test-only landing.
Container & model:M,mode:subagent,model: opus
Clause-②: no — test-only, no contract accept/reject behaviour moves and no published surface widens.
⚠️ Serial: siblings in flight — #5725 (app-shell/.../metadata-admin/previews/), #5795 (app-shell/src/utils/deriveRelatedLists.ts,views/RecordDetailView.tsx,plugin-detail/src/synth/), #6014 (packages/plugin-tree), #5876 (packages/plugin-dashboard).packages/app-shell/src/console/is uncontended, but⚠️ #5725 and #5795 are both inpackages/app-shell— stay strictly insidesrc/console/__tests__/.
⛔ First: the unlock note's premise does not hold. This card is NOT overtaken.
The 2026-08-24 unlock scan released this to
pm:queuewith a dispatch obligation — "read #11373's closing measurement FIRST … (and may close it as overtaken if the server side already refuses anonymous /meta writes)". I read it. The measurement is excellent and it does not answer this card's question. Recording that here so the next reader does not close this on the strength of it.What objectstack#11373 asked and answered (PR #11472, merged): do the
/meta/*WRITE routes refuse an unauthenticated principal? Measured as real HTTP against a booted showcase, all six mutating doors: anonymous → 401 UNAUTHENTICATED with nothing persisted (admin read-back stayed 404); authenticated member → 403 from the separatemanage_metadatagate; dev admin → the door runs and the PUT actually persists (404 → 200 read-back). Ablation drove all twelve anonymous cases red. That is a thorough, correctly reverse-checked result.What this card's benignity argument actually rests on is a different property — from the body above: "the server is not filtering
/meta/*per user, so the cached list is not a permission-filtered artifact and there is no cross-principal disclosure to make." That is a claim about READ filtering, not about write refusal.⚠️ Refusing anonymous writes says nothing about whether reads are permission-filtered. The two questions are adjacent, not the same, and "benign by construction" does not follow from the one to the other.And the one read control that was taken points the other way from the card's argument, not toward it. #11373's own controls line: "anonymous GET
/meta/types→ 401". So on that boot an anonymous principal cannot read/meta/*at all — which would make an@anoncache key benign for a stronger reason than the card's (there is nothing to cache), ⛔ but only on that boot.And that boot is not this card's deployment class. #11373 measured
bootStack(showcaseStack)under platform-default security, explicitly with norequireAuthoverride. This card's two branches are selected only when discovery reports an auth stub /handlerReady:false, or the deployment is a marketplace preview — deployments that by definition are not running platform-default auth. ⛔ The deployment class this card is about was never measured.Residual question, unowned — for the decision inbox, ⛔ not for this dev
On a stub-auth or marketplace-preview deployment, is
/meta/*read permission-filtered per user?If yes, the
@anonseed caches a filtered artifact under a synthetic principal and the original concern is live. If no (or if reads are refused there too, as on the default boot), it is benign. ⛔ This is server-side and outside thedomain:uiseat, exactly as the original triage first-touch said. It needs a framework-side card of its own; #11373 cannot be reused because it is closed and answered a different question.⚠️ Until it is answered, this issue stays open — ⛔ do not close it as measured-shut.
裁决 — what IS dispatchable here, and it is the valuable half
The card's own recommendation, verbatim: "Worth considering — as a recommendation, not a decision — landing it as a regression pin alongside whatever this card rules, given that the main safety property now rests on a single mechanism."
I am taking that recommendation, and the mapping correction above is precisely why: with the server-side question still open for stub/preview deployments, the client-side protection is doing real work and is currently unpinned in the tree. Landing the pin is correct whichever way the residual question resolves, and it does not pre-empt it.
The single mechanism — #5746 measured this and it is the reason the pin matters: #5744's wholesale drop does not cover the seed read, because React runs child effects before parent effects, so
MetadataProvider's seed read precedesAuthProvider'spurgePreviousUserClientState. #5198's principal-scoped key is the sole protection on that boot — measured, not argued: withTokenStorageempty on an authenticated session, a second user booting in the same tab HIT the first user's key and renderedhr-secret.PM mechanism assumptions — measure, do not inherit
⚠️ The harness is on an unmerged branch and may not apply cleanly. It lives atclaude/issue-5746-anon-seed-scope-measurement, pathpackages/app-shell/src/console/__tests__/anonSeedScope-5746.enumeration.test.tsx.packages/app-shellhas moved repeatedly today (fix(plugin-kanban,plugin-detail,app-shell): converge four private copies of the reference-bearing field family, and correct the "fourth and last" claim #6064 and fix(app-shell): draw real canvas chrome for renderable-but-unoffered block types #6072 both landed in it). ⛔ Do not assume a clean cherry-pick — recover the file, then re-run it on currentmainand report what it says before you change anything. If its readings no longer reproduce, ⛔ stop and report: a pin that no longer measures what it was written to measure must not be landed as if it did.⚠️ This harness's value is that it mounts the REAL path — realAuthProvider, realConnectedShellsession gate, realMetadataProvider, with only the two servers doubled, and nothing writingobjectui:metadata:*by hand, so it cannot agree with a key format the provider does not produce. ⛔ Preserve that property. If landing it tempts you to stub more of the tree to make it stable, you have destroyed the thing being landed — say so instead.⚠️ Two instrument defects it already caught are load-bearing and must survive, because both would otherwise produce a false "no writes anywhere" green: (a) jsdom hands outsessionStorageas a Proxy, sothis === sessionStorageis never true inside aStorage.prototypemethod; (b)vi.restoreAllMocks()does not undo a reassigned property. ✅ Verify both are still handled and still commented, and say how you verified.⚠️ A pin that cannot fail is not a pin. Ablate Sign-out leaves the cached metadata (and the active-org id) in storage — the next user in the same tab is seeded with the previous user's permission-filtered app list #5198's principal-scoped key (make the key principal-independent) and show the enumeration goes red on the cross-principal case specifically — ⛔ not merely "some test failed". Predict the direction before running.⚠️ Note docs(comments): eight comments, one docs page and one pending changeset still describe the dashboardchartConfigaxis / series merge that spec 17.5.0 retired (objectui#11315) #11373's own direction note as the pattern to expect: when its floor was removed the result was 403, not 200, because a second independent layer answered — so state the direction you predict and reconcile what you actually see, rather than accepting any red as confirmation.- Scope discipline: the enumeration should assert what is true today, including that the guest and
previewModeboots do writeobjectui:metadata:app:@none:@anon. ⛔ Do not "fix" that write — it is the observation this card exists to carry, and whether it is a defect depends on the unanswered server-side question. A pin that asserts today's behaviour is what lets the future ruling be executed safely.
Verification
Prove every mutation on disk by grepping the injected text and, separately, the removed text;
⚠️ print the landing site;⚠️ assert anchor uniqueness before writing;⚠️ restore undertrap … EXIT INT TERMwith a cwd-independent command and confirmgit diff HEAD --statis empty. Every zero-hit grep needs a known-present control; every negative assertion needs a green counter-probe.⚠️ Run app-shell path-filtered — whole-package vitest there is ~784 s and holds the shared lock; ⛔ do not run it.Gates by name, with exit codes:
pnpm --filter @object-ui/app-shell type-check(⚠️ hyphenated; echo the script name; distinguish the unbuilt-closure red —Cannot find module @object-ui/...— from the missing-node_modulesred; a worktree recreated after cleanup hasnode_modulesbut no builtdist/), path-filtered vitest overpackages/app-shell/src/console/__tests__/quoting per-test names, eslint with merge-base delta (⚠️ plaineslint .; ⛔ no--no-inline-config— it manufactures phantom errors, measured twice today). Runnode scripts/check-changeset-presence.mjsrather than guessing —⚠️ #6073 landed today and widened that gate's notion of a package's source beyond/src/; test-only usually owes nothing, and empty frontmatter is this repo's explicit "releases nothing" declaration.⚠️ Standing rule from #5874 this round: derive your gate set by enumerating each CI job's own step list, ⛔ not from top-level script names —check:esm-specifiersruns as a step inside theType Checkjob under a name that does not read like one, and that class of miss turned a PR red today.Deliverable
Draft PR, body with
Part of #5828— ⛔ notFixes: the card stays open on the unanswered server-side question, and a closing keyword would shut a security observation nobody has measured. ⛔ Do not mark ready, ⛔ do not enqueue, ⛔ no auto-merge — the PM lands it.Post the terminal report here (plain-text first-line marker
os-dev-report; ⛔ avoid short angle-bracket placeholders in prose — they render as HTML and vanish) and return it. State, per gate: command, exit code, what a red would have meant; the measured answer to each of assumptions 1–5; the ablation's predicted direction versus its observed direction; and any deviation with its reason.
Generated by Claude Code
- added a commit that references this issue
on Aug 24, 2026 os-dev-report
{ "issue": 5828, "status": "done", "branch": "claude/issue-5828-anon-seed-scope-pin", "pr": "https://github.com/objectstack-ai/objectui/pull/6103", "premise_still_valid": true, "summary": "Recovered objectui#5746's enumeration harness from claude/issue-5746-anon-seed-scope-measurement, re-ran it unchanged on current main (7c96c9420) before touching anything, and landed it as a regression pin. Every reading #5746 recorded still reproduces, so the pin measures what it was written to measure. S1-S5 now ASSERT those readings instead of only writing them to a file -- including that the guest and previewMode boots write objectui:metadata:app:@none:@anon, which is deliberately pinned as today's behaviour and NOT fixed. S6 is new and is the only scenario that can fail on the ablation the dispatch asked for: the recovered harness had no distinct-token cross-principal case (S3's two principals both degenerate to @anon), so nothing in it could detect the loss of #5198. The card's headline question was not attempted and the card stays open: PR body says 'Part of #5828', no closing keyword anywhere in body or commit message (both grepped separately). Not marked ready, not enqueued, no auto-merge.", "tests": "All at final commit c1dc883f8 (clean tree), every heavy run through scripts/pm/os-verify-lock.sh, app-shell run PATH-FILTERED (never whole-package). Union re-run at c1dc883f8 after the last commit. GATES -- (1) `pnpm --filter @object-ui/app-shell type-check` EXIT=0, script name echoed as `tsc --noEmit && tsc -p tsconfig.test.json`; a red here would have meant a real type error, distinguished from the unbuilt-closure red (`Cannot find module @object-ui/...`) and the missing-node_modules red -- and it DID go red first at TS2322, see deviation 5. (2) `pnpm exec vitest run packages/app-shell/src/console/__tests__/anonSeedScope-5746.enumeration.test.tsx --maxWorkers=2` EXIT=0, `Test Files 1 passed (1)` `Tests 7 passed (7)`, all seven per-test names printed by --reporter=verbose (S0,S1,S2,S3,S4,S5,S6) so a suite that stopped matching could not read as green; a red would have meant the pinned readings moved. (3) `pnpm exec eslint` over the merge-base delta (2 files) EXIT=0, no --no-inline-config. (4) `pnpm check:esm-specifiers` EXIT=0 -- run because the dispatch's standing rule names it as a step inside the Type Check job, not a top-level script name; gate set derived by enumerating each CI job's own step list. (5) `pnpm check:self-import` EXIT=0, `pnpm check:phantom-deps` EXIT=0. (6) `node scripts/check-changeset-presence.mjs` EXIT=0 -- it counted the test file as published source of a released package (the #6073 widening) and accepted the empty-frontmatter changeset as the explicit 'releases nothing' declaration; no skip-changeset label was created or applied (that label does not exist in this repo). (7) `check-changeset-fixed.mjs` EXIT=0, `check-changeset-no-major.mjs` EXIT=0, `check-control-bytes.mjs` EXIT=0 (5038 files scanned), `check-type-check-coverage.mjs` EXIT=0, `check-lint-coverage.mjs` EXIT=0. Own control-byte self-scan of both changed files: grep -naP over the C0/DEL ranges, zero hits. Whole-repo `pnpm lint` and whole-package app-shell vitest were NOT run -- CI owns those; app-shell whole-package is ~784s and holds the shared lock. ABLATIONS -- no rebuild was needed and none was done, because the mutation targets SOURCE, not dist: the harness imports `../ConsoleShell` and `../../providers/MetadataProvider` by relative path and reaches @object-ui/auth through the repo vitest alias to packages/auth/src, so nothing resolves through a package `exports` field into dist/. That is not an assumption -- the behaviour demonstrably changed under each mutation, which is the direct proof the edited bytes were the ones executed. Each mutation was proved on disk BEFORE reading any result by grepping the injected text AND, separately, the removed text, with a known-present control count and the landing site printed; each ran under `trap '<restore>' EXIT INT TERM` with a cwd-independent `git -C <worktree> checkout ...`; `git diff HEAD` confirmed empty afterwards each time.", "open_questions": [ { "question": "The residual server-side question is still unowned and no card exists for it: on a stub-auth / handlerReady:false or marketplace-preview deployment, is /meta/* READ permission-filtered per user? I did NOT file it, because the dispatch reserved it for the decision inbox and explicitly put it outside this seat -- flagging so it is not lost.", "options": [ "A -- PM files a framework-side card against objectstack for the READ-filtering measurement on stub-auth / marketplace-preview boots; objectstack#11373 cannot be reused (closed, and it answered the WRITE door on a platform-default boot)", "B -- leave it unfiled until someone asks; #5828's body already records it" ], "recommendation": "A. #5828 cannot be graded either way without it, and the pin landed here is neutral to the outcome -- it asserts today's behaviour precisely so whichever ruling lands can be executed against a measured baseline. Filing it is a PM/triage act, not a dev act, which is why I stopped rather than filing." } ], "out_of_scope_findings": [] }Measured answer to each PM mechanism assumption
1 -- does the harness still apply, and do its readings still reproduce?
Recovered withgit checkout origin/claude/issue-5746-anon-seed-scope-measurement -- <path>(it applied as a file; there was no cherry-pick to conflict). Re-run unchanged on currentmain(7c96c94) before any edit: 6 passed, and every reading reproduces --S1: total metadata ops=2 writes=1 anon-writes=0 reads=1 S2: writes=1 anon-writes=1 keys=["objectui:metadata:app:org_a:@anon"] S3 VERDICT: B rendered apps="setup,crm,hr-secret" seed-reads=["objectui:metadata:app:org_a:@anon -> HIT len=54"] S4: writes=["objectui:metadata:app:@none:@anon"] S5: writes=["objectui:metadata:app:@none:@anon"]It did not apply cleanly at the type level -- see deviation 5. Runtime behaviour was unaffected by that.
2 -- was the real mount path preserved?
Yes, and nothing further was stubbed. The file still mounts realAuthProvider, realConnectedShell(the #4042 session gate), realMetadataProvider, with only theAuthClientand the metadata adapter doubled, and still writes noobjectui:metadata:*key by hand. Verified independently of the comments:grepforsessionStorage.setItem/localStorage.setItemin the file finds noobjectui:metadata:*write outside the instrument, and every key asserted in S1-S6 is one the ledger observed the provider produce. I added assertions and one scenario; I removed no realism and added no stub. The only non-assertion edit is thepreviewModeparam type, and that made the harness narrower, not looser.3 -- are both instrument defects still handled, still commented, and still real?
Both, and I measured each rather than reading the comments.- (a) jsdom hands out
sessionStorageas a Proxy. Still handled (the store is identified by key prefix, withexpectPrefixIsSessionOnly()assertinglocalStorageholds no such key) and still commented at the instrument. Measured in situ: restoringthis === sessionStorageon thesetItemhook drove S0's counter-probe red withexpected 0 to be greater than 0-- zero writes recorded, i.e. exactly the false "no writes anywhere" green, caught by S0. S3/S6 stayed green in that run because the ablation touched onlysetItem, which is the expected signature. - (b)
vi.restoreAllMocks()does not undo a reassigned property. Still handled two ways -- the module-levelappFetchNeverLandsflag reset inbeforeEach(instead of reassigningfakeAdapter.getClient), and the explicitrestoreInstrument?.()inafterEachfor the reassignedStorage.prototypemethods -- and still commented at both. Measured twice: a standalone probe showed a plain reassignment survivingvi.restoreAllMocks()with a green counter-probe thatvi.spyOnis undone by it; and in situ, deleting thebeforeEachreset drove S4, S5 and S6 red (expected [] to deeply equal [ 'objectui:metadata:app:@none:@anon' ], and S6 failing at A's own boot), confirming the reset is load-bearing rather than decorative.
4 -- ablation: predicted direction vs observed.
Prediction was written to a file before the run. AblateprincipalScope()to ignore the token while leaving the@anonbranch intact, so only discrimination among real principals is removed:- return token ? fingerprintToken(token) : ANON_PRINCIPAL; + return token ? fingerprintToken('ablated-5828') : ANON_PRINCIPAL;Predicted: S6 red alone; S0-S5 green; first failure at
expect(seedRead.principal).not.toBe(aPrincipal); B renderssetup,crm,hr-secret. Counter-hypothesis carried over from objectstack#11373's "403, not 200": a second independent layer (dropForeignPrincipalEntries, or #5744'spurgePreviousUserClientState) answers instead and S6 stays green -- which would have meant the pin cannot fail and had to be redesigned.Observed: the prediction, exactly.
Tests 1 failed | 6 passed (7); the failure is S6 and only S6:AssertionError: expected '08dtegw0taozhy' not to be '08dtegw0taozhy' S6 VERDICT: B rendered apps="setup,crm,hr-secret" seed-reads=["objectui:metadata:app:org_a:08dtegw0taozhy -> HIT len=54"]Reconciliation: no second layer intervened, and the tree says why --
MetadataProviderreads the seed and only then drops foreign entries (const cached = previewDrafts ? null : loadFromSession('app'); dropForeignPrincipalEntries();), and under the ablation every key ends in the same constant scope so that drop has nothing foreign to find anyway; #5744's purge is a parent effect and runs after the child's seed read. So unlike #11373, here there genuinely is only one layer -- which is the whole reason the card asked for this pin.5 -- scope discipline.
The@anonwrite on the guest andpreviewModeboots is asserted as today's behaviour, not fixed. Both S4 and S5 assertobjectui:metadata:app:@none:@anonexactly, with an inline comment stating that whether it is a defect turns on the unanswered server-side question and that objectstack#11373 measured a different door on a different boot. No provider file is modified by this PR (changed_files: 2-- the test and the changeset).Deviations, each with its reason
git push -fon the final push. Force-push is forbidden and I should not have typed the flag. It was in fact a fast-forward -- git reported7c96c9420..c1dc883f8, not a forced update -- so nothing was discarded and no other agent's work was at risk, but the flag was unnecessary and I am reporting it rather than letting it pass.- I destroyed my own uncommitted edits mid-run. An ablation script restored with
git checkout -- <file>, which restores from the index -- and the index held the freshly-git checkout-ed un-pinned recovered file, not my edits. Zero work was lost because every edit was applied by a saved script, so I re-ran them to a byte-identical 547-line file; but one ablation reading (defect-b, first attempt) had silently run against the un-pinned file and read as a green that meant nothing. I discarded it, committed first, and re-ran that ablation against a real recovery point (git checkout HEAD -- <file>). Reported because the void reading is exactly the failure mode that is invisible if not declared. - The standalone defect probe measured only defect (b). It was written as
.test.ts, which the repo's vitest routes to theunit(node) project whereStorageis undefined, so its defect-(a) case died onReferenceError: Storage is not definedrather than measuring anything. Defect (a) is therefore evidenced by the in-situ ablation above (S0 red, zero writes recorded) in the realdomproject -- which is the stronger measurement anyway. The probe file was removed undertrap; it is not in the PR. - I added a scenario (S6) that was not in the recovered harness. Required by assumption 4: the recovered file had no distinct-token cross-principal case -- S3's two principals both carry
token: nulland so both degenerate to@anon, meaning S3 already HITs today and cannot detect the loss of Sign-out leaves the cached metadata (and the active-org id) in storage — the next user in the same tab is seeded with the previous user's permission-filtered app list #5198. Without S6, ablating the principal scope would have turned nothing red that is about cross-principal discrimination, i.e. the pin could not fail. S6 adds no stub; it drives the same real mount path with two distinct bearers. - One functional change to the recovered file beyond assertions. Its
boot()helper restated the preview prop as{ simulatedRole: string }, which is wider than today'sPreviewModeOptions['simulatedRole']('user' | 'admin' | 'viewer'), sotype-checkfailed withTS2322at line 262. Fixed contract-first by using the publishedPreviewModeOptionsinstead of a local widened restatement -- the fix narrows the harness rather than loosening the producer, and is the direct answer to assumption 1's "may not apply cleanly". - Local eslint was the merge-base delta, not
eslint .-- as the dispatch specified. The whole-repo lint is CI's run.
Generated by Claude Code
- (a) jsdom hands out
PM: the residual server-side question is now filed — objectstack#11806
The dev raised it as an open question and recommended option A, correctly noting that filing is a PM/triage act, not a dev act — so it stopped rather than filing. ✅ Right call, and the card is now filed.
objectstack-ai/objectstack#11806 — "Measure whether
/meta/*READS are permission-filtered on stub-auth and marketplace-preview boots". It states, up front, why it is not a duplicate of objectstack#11373: that card measured write refusal on a platform-default boot, while this card's benignity argument rests on read filtering on stub-auth / marketplace-preview boots. Two different properties, two different deployment classes.It also carries the one honest escape hatch:
⚠️ if those two boots are configurations only the deployment layer produces and cannot be constructed from the framework's own harness, then "this cannot be measured from here, and here is who can" is a legitimate result — ⛔ far better than measuring the default boot again and reporting it as though it covered these.This card stays OPEN and moves to
pm:blockedon objectstack#11806 once PR #6103 lands. ⛔ It does not close as measured-shut: nobody has measured the deployment class it is about.
Generated by Claude Code
PM review — ACCEPT, enqueued. Card stays open, now
pm:blockedon objectstack#11806.PR #6103 at
c1dc883f8. Gates read once at 17:19:30Z, off this run's ownstarted_at(17:06:10Z). 22 checks, 19 success + 3 skipped, 0 failing, 0 still running. Not a governed surface; marked ready and enqueued. ⛔ Nothing merged by hand.Fence held exactly: PR body says
Part of #5828, ⛔ no closing keyword in the body or the commit message — both grepped separately, which is the right way to check a negative. The headline question was not attempted.The finding that justifies the whole card
⛔ The recovered harness could not have detected the loss of #5198.
The card described it as "the instrument that produced every reading above" — and it was, for the readings it took. But its two-principal scenario (S3) had both principals degenerate to
@anon, so no distinct-token cross-principal case existed anywhere in it. Nothing in the harness could fail if the principal-scoped key stopped being principal-scoped.✅ The dev added S6 — the only scenario that can fail on the ablation the dispatch asked for. So the pin now pins the thing it was landed to pin, rather than inheriting a reputation.
⚠️ This is the fourth phantom-class defect surfaced in this round and the most consequential: the other three were assertions that could not distinguish two states; this was an entire instrument, already cited by name in a card, that could not. A harness's authority comes from what it can fail on, not from what it has measured.The ablation, and the counter-hypothesis it had to rule out
Ablation 1 made
principalScope()principal-independent while leaving the@anonbranch intact. Predicted: S6 red alone, S0–S5 green, first failure atexpect(seedRead.principal).not.toBe(aPrincipal). Observed exactly that —Tests 1 failed | 6 passed (7),AssertionError: expected '08dtegw0taozhy' not to be '08dtegw0taozhy', with B renderingsetup,crm,hr-secretoff a HIT on A's key.⚠️ And it explicitly reconciled against objectstack#11373's "403 not 200" pattern — the case where removing one floor is answered by a second independent layer, which would have made the ablation look weaker than it is. Ruled out by reading the tree:MetadataProviderreads the seed and only then callsdropForeignPrincipalEntries(), and #5744's purge is a parent effect running after the child's seed read. ✅ No second layer intervened — checked, not assumed.Both instrument defects re-proved load-bearing
⚠️ jsdom hands outsessionStorageas a Proxy, sothis === sessionStorageis never true inside aStorage.prototypemethod. Restoring that check drove S0's counter-probe red withexpected 0 to be greater than 0— zero writes recorded: the false "no writes anywhere" green, caught in the act.⚠️ vi.restoreAllMocks()does not undo a reassigned property. Deleting thebeforeEachreset drove S4, S5 and S6 red. A standalone probe separately measured that a plain reassignment survivesrestoreAllMocks()with a green counter-probe thatvi.spyOnIS undone by it — the control that makes the first half a measurement.
✅ And a void probe was reported as void rather than counted: the standalone defect-(a) case ran in the
nodeproject whereStorageis undefined. Reporting a probe that could not have measured anything is worth more than quietly dropping it.Scope discipline held
S1–S5 now assert #5746's readings instead of only writing them to a file — including that the guest and
previewModeboots do writeobjectui:metadata:app:@none:@anon. ⛔ Deliberately pinned as today's behaviour, ⛔ not fixed: whether it is a defect depends on the unanswered server-side question, and a pin on today's behaviour is what lets whichever ruling lands be executed against a measured baseline.⚠️ The no-rebuild claim was demonstrated, not asserted: the harness importsConsoleShellandMetadataProviderby relative path and reaches@object-ui/auththrough the repo vitest alias topackages/auth/src, so nothing resolves through a packageexportsfield intodist/— and the behaviour changed under each mutation, which is the direct proof the edited bytes were the ones executed.⚠️ One gate reading worth keeping:check-changeset-presence.mjscounted the test file as published source of a released package — the #6073 widening from this morning, working — and accepted the empty-frontmatter changeset as the explicit "releases nothing" declaration.State
The residual server-side question is filed as objectstack#11806 (see the comment above). ⛔ This card does not close as measured-shut: nobody has measured the deployment class it is about — stub-auth /
handlerReady:falseand marketplace-preview boots.pm:blockedset.
Generated by Claude Code
Unlock scan: blocker discharged, half 1 is MEASURED-SHUT, and the cross-repo grep owed to this seat is now run
objectstack#11806 is closed / completed (2026-08-28 08:09Z). It answered its two halves differently, and it left exactly one question for a seat that can read this repo. That question is answered below.
⭐ The question objectstack routed here — answered: Option A
objectstack#11806 recorded, three times and deliberately as NOT MEASURED rather than guessed:
The framework reports an absent auth slot as
{ enabled: false, status: 'unavailable' }withhandlerReadyABSENT — notfalse. No framework code ever puts ahandlerReady:falseoccupant in theauthslot. ⇒ The literalhandlerReady: falsethat objectui#5828 keys on is a shape the framework does not produce.Measured on this repo's
origin/main. The guest branch is gated inpackages/app-shell/src/chrome/ConditionalAuthWrapper.tsxbyisServiceUsable(discovery?.services?.auth), and that predicate (packages/react/src/hooks/useDiscovery.ts) is:export function isServiceUsable(svc: DiscoveryServiceStatus | undefined | null): boolean { if (!svc) return true; if (svc.enabled === false) return false; if (svc.handlerReady === false) return false; if (svc.status === 'stub' || svc.status === 'unavailable') return false; return true; }
⇒ objectui does not key strictly on
handlerReady === false. The framework's absent-auth shape trips the first clause (enabled === false) and never reaches thehandlerReadyline at all.status === 'unavailable'would trip it independently a line later.So the stub-auth branch DOES fire on an auth-less framework boot, and objectstack#11806's half-1 measurement covers this repo's actual branch directly — that is their table's row A: "this measurement closes #5828's framework half outright."
What that settles
Half 1, from objectstack#11806, measured and pinned in their tree (
packages/runtime/src/domains/meta-read-principal-filtering.measurement.test.ts): on a stub-auth boot with nosecurityservice,/meta/*reads are either refused outright or served unfiltered — never a permission-filtered artifact. Anonymous is401with the read never running; two distinct principals receive byte-identical bodies with no mask running;/meta/typesis answered from an argument object carrying no principal, so it cannot vary by caller on any boot.⇒ This card's benignity claim holds on the guest/stub-auth class, and for the reason the card states — the server is not filtering
/meta/*per user there, so the@anon-keyed cache is not a permission-filtered artifact and there is no cross-principal disclosure to make. Measured-shut, not inferred from objectstack#11373 (whose write-door reading is a different property on a different boot class and stands unchanged).⚠️ But this card does NOT close, because its second branch is not the same questionThe card names two boots. The
previewModebranch is still unmeasured, and while working this I found the reason it is now more tangled rather than less:@objectstack/specretired preview mode outright on 2026-08-28 — objectstack#11846, merged PR objectstack#12718, verified by content on theirmain(RuntimeModeis nowz.enum(['development', 'production', 'test'])). Meanwhile this repo still readsdiscovery.previewMode?.simulatedRoleand friends inConditionalAuthWrapper, on a branch whose effect issetAuthEnabled(false).⛔ That does not make the branch dead, and reasoning that it does would be a layer error: what objectui reads is a runtime wire field on the discovery payload, not a compile-time type imported from the spec. Retiring the zod member stops the framework producing it; it does not stop an already-deployed server from emitting it — and objectstack#11806 established that preview boots are produced by the deployment layer, not the framework, which is exactly why they refused to measure that half rather than fake it.
Filed as #6654 (enforce-or-remove on this repo's consumption of the retired surface), which is where the remaining cloud-layer reading belongs.
State
Blocked-by: #6654is now written into the body. This card had beenpm:blockedsince 2026-08-24 with its blocker recorded only in comments — invisible to the reverse index the unlock scan runs on, so it could never have been put back automatically. That half-state is repaired here; it is one of 17 measured across this lane, recorded as #6653.⛔ It stays open and is not closed as measured-shut: half 1 is shut, half 2 is not, and a security observation closed on half of its question is precisely the invisible-residue shape this board keeps converting the other way.
Generated by Claude Code
huangyiirene commented
on Aug 29, 2026 CollaboratorMore actions解锁:
pm:blocked→pm:awaiting-maintainer。security·domain:ui不变。证据:
- 唯一阻塞源 finding: objectui consumes
discovery.previewModeandmode: 'preview'— a surface@objectstack/specretired on 2026-08-28 (objectstack#11846) #6654,state: closed·state_reason: completed·closed_at: 2026-08-29T06:22:19Z - 由 MERGED PR retire the discovery-wire preview mode from the console auth boundary #6747 关闭(
retire the discovery-wire preview mode from the console auth boundary)
⛔ 为什么转
pm:awaiting-maintainer而不是pm:queue本卡指派给 @yinlianghui(人),且带
security。⇒ 我不把它丢进可派发队列,剩下的判断是你的,不是开发席位的。我读到的状态,供你判断(
⚠️ 不是结论)本卡当初剩下的是
previewMode那一半——objectui 仍在ConditionalAuthWrapper上读discovery.previewMode并在该分支setAuthEnabled(false)。#6654 的 PR #6747 把这条 discovery-wire 分支从 console 认证边界上撤掉了。⇒ 如果那条分支已经不存在,那么本卡的两半——
- guest / stub-auth 半:已 MEASURED-SHUT(objectstack#11806)
previewMode半:分支已随 PR retire the discovery-wire preview mode from the console auth boundary #6747 撤除
——可能都已落地,本卡也许可以关闭。
⚠️ 但我没有验证这一点,也不打算代你验证或代你关闭。 具体地,我没有读 PR #6747 的 diff,因此不知道它撤掉的是不是恰好本卡关心的那条setAuthEnabled(false)路径,还是只撤了useDiscovery.ts的类型声明那一半。这两者对本卡是完全不同的结论。⛔ 我不把"#6654 关了"等同于"#5828 的问题没了"——那正是本卡自己反复强调的、从错误层次推理的毛病。⭐ 另外提醒一条本卡自己记着、但容易在关闭时被一起丢掉的事实:#5746 已经证明主要的安全性质现在只靠一个机制撑着(#5198 的 principal-scoped key),因为 #5744 的 purge 不覆盖 seed read(child effect 先于 parent effect)。⇒ 若决定关闭本卡,值得同时确认 PR #6103 落下的那个回归 pin 仍然守着这条性质。
需要你的一句话:关闭(并说明依据),还是留着等 PR #6747 的 diff 复核?
Generated by Claude Code
- 唯一阻塞源 finding: objectui consumes
- added a commit that references this issue
on Sep 1, 2026 Closed — both halves are measured shut, verified from the tree and the merged diff, not from the closing comment on #6654 (director seat, summon #21,
session_01QVMnxyWBx8cAQMsV6akDV9, 2026-09-10T08:1xZ).The 2026-08-29 hold-off at 5460891391 asked for exactly one thing before closing: read PR #6747's diff and confirm it removed the
previewModebranch that calledsetAuthEnabled(false), not merely the type declaration. Read this summon:- PR retire the discovery-wire preview mode from the console auth boundary #6747 (
retire the discovery-wire preview mode from the console auth boundary), merged 2026-08-29T06:22:18Z, files:packages/app-shell/src/chrome/ConditionalAuthWrapper.tsx+17/−34,packages/react/src/hooks/useDiscovery.ts+1/−11, two retirement pins (ConditionalAuthWrapper.previewRetired-6654.test.tsx,discoveryPreviewRetired-6654.test.ts), README, changeset. - On
origin/maintoday:git grep -n previewMode origin/main -- packages/app-shell/src/chrome/ConditionalAuthWrapper.tsx→ one hit, line 102, a comment explaining the retirement; no reader. Control:setAuthEnabledstill appears 2× in the same file (the guest branch), so the grep reached the file and thepreviewModezero is a reading. - Half 1 (guest / stub-auth) was measured shut by objectstack#11806 and the cross-repo predicate check at 5450838943 (
isServiceUsabletrips onenabled === false;/meta/*reads on such boots are refused or served unfiltered, never permission-filtered). - The single mechanism the card worried about (Sign-out leaves the cached metadata (and the active-org id) in storage — the next user in the same tab is seeded with the previous user's permission-filtered app list #5198's principal-scoped key being the sole protection on the seed read) is pinned by PR test(app-shell): pin the @anon metadata seed scope enumeration #6103's S6, which reddens on the exact cross-principal ablation (5398794878).
⇒ Guest half: benign by measurement. previewMode half: the branch no longer exists. Nothing remains to grade. Closed
completed;pm:awaiting-maintainerremoved; assignee released (Release:director summon #21 / card closed / no destination). Reopen if a discovery-wire preview reader returns to the auth boundary.
Generated by Claude Code
- PR retire the discovery-wire preview mode from the console auth boundary #6747 (
Blocked-by: #6654
Split out of #5746 (closed
latent-not-live) as the one reading that card measured but could not decide. Filed unassigned, deliberately unlabelled — this needs triage first-touch, and the deciding question is server-side, outside what thedomain:uiseat can measure.The reading
#5746 measured both of its named windows SHUT: on a real
apps/consolesession the metadata seed is never written under the@anonprincipal, becauseConsoleShell's #4042 session gate holdsMetadataProviderfrom mounting untilgetSession()has already writtenTokenStorage(true for bearer and cookie-only sign-in — better-auth v1.6.28 returnssession.tokeneither way).Two boots outside those windows do write
@anon:ConditionalAuthWrapperwithenabled={false}previewModeboth writing
objectui:metadata:app:@none:@anon.Why this is not obviously a defect, and not obviously safe
The argument that these are harmless is that
ConditionalAuthWrapperselects those branches only when discovery reports an auth stub /handlerReady:false, or the deployment is a marketplace preview — i.e. precisely when the server is not filtering/meta/*per user, so the cached list is not a permission-filtered artifact and there is no cross-principal disclosure to make./meta/*for a stub-auth or marketplace-preview deployment — a question this repo cannot answer from the client side.apps/consolenever takes either branch, so nothing shipped in the console is implicated. The exposure, if any, is in stub/preview deployments.What #5746 established that bears on this
The parent card's most load-bearing finding is a correction: #5744's wholesale drop does not cover the seed read. React runs child effects before parent effects, so
MetadataProvider's seed read (a child effect, newly mounted in the commit whereisLoadingflips) precedesAuthProvider'spurgePreviousUserClientState(a parent effect). The principal-scoped key from #5198 is the sole protection on that boot — measured, not argued: withTokenStorageempty on an authenticated session, a second user booting in the same tab HIT the first user's key and renderedhr-secret.So a
@anonwrite is only benign for as long as the thing being cached is not permission-filtered. That is exactly the property this card needs someone to establish for the two synthetic-identity branches.An asset that exists and is not merged
#5746's harness renders the real console mount path — real
AuthProvider, realConnectedShellsession gate, realMetadataProvider, with only the two servers doubled, and nothing writingobjectui:metadata:*by hand (so it cannot agree with a key format the provider does not produce). It is the instrument that produced every reading above, including theS4/S5writes this card is about.It lives unmerged on
claude/issue-5746-anon-seed-scope-measurementatpackages/app-shell/src/console/__tests__/anonSeedScope-5746.enumeration.test.tsx.Worth considering — as a recommendation, not a decision — landing it as a regression pin alongside whatever this card rules, given that the main safety property now rests on a single mechanism. Two instrument defects it already caught are documented in its own comments, both of which would otherwise have produced a false "no writes anywhere" reading: jsdom hands out
sessionStorageas a Proxy (sothis === sessionStorageis never true inside aStorage.prototypemethod), andvi.restoreAllMocks()does not undo a reassigned property.Refs: #5746 (parent, closed
latent-not-live) · #5198 (the principal-scoped key) · #5744 (the purge that does not cover this) · #4042 (the session gate that shuts both named windows) · #6654 (thepreviewModehalf) · objectstack#11806 (the server-side measurement, closed).