Repository navigation
Scheduled tasks page stays on Loading…: the schedule list is neither readable by a signed-in actor nor scoped to one - #2112
Conversation
… own rows The list is read with no payload at all, and a host with a member signed in refused every read: the page stayed on its loading state. Listing is a read, so any signed-in actor may ask, and the data layer narrows the answer to the caller's own rows with the same ownership predicate a copy's source and an edit's own Session already use. A read that fails now says so: the client keeps the refusal beside the shared view and the page prints it instead of polling into "Loading…" forever. The host's own digest create sends the assistant's owner with the request, because it reads that answer back for the id of the row it just wrote. Docs: team.md and core-host.md describe the list's rule and the owner that travels with the digest create.
|
Self-test: PASS at fa6982a Ran: base Advisory self-test on my own trees (base Red first (base
After (tip, ×3): 35/35, 41/41, 27/27. Mutations (each alone, reverted after)
Existing assertions: 0 changed. The only test line touched is the destructuring import in Own read: with two owners signed in, each list answers only its own rows — neither the other's title, prompt nor Session is in the wire answer — and an actor-less call with scoping on answers no rows. verify ( Not verified: real browser rendering (the sentence is a pure function; the suite has no renderer), |
|
Self-test: PASS at fa6982a Ran: Advisory on the security surface only — not a repository conclusion. The repository verdict belongs to the reviewer named on the pull request. As of my read: 1 comment, newest 6569099136. The gate opens the list and nothing else. The rows narrow in the data layer, where the answer is built. Both loops in The actor reaches every path that reads the list. The channel hands it to the list and to pause/resume/retry/delete; create and update already carried it. No other production call site reaches the view without an actor: the only remaining actor-less calls are in the Project Assistant's delete and pause/resume helpers, which never read the returned rows for a decision (see the note below). The digest create carries its owner, and nothing else was loosened. The assistant's schedule request now goes in as that Bot's owner, which is what lets the host read back the row it just wrote; the same Bot's owner is what the per-row rule compares against, so this is the narrow fix, not a bypass. No other read in that file changed. The failure sentence is a browser sentence, not model input. A refused or failed list read now publishes Nothing sensitive in the change set. No secrets, tokens or credentials; no Two notes for the owner, neither a leak:
|
|
Verdict: PASS at fa6982a Ran at this tip: Review points, against the 21-file compare (
Follow-up, not blocking: on a room whose Bots span owners (a shared room, no single Metadata tidied on this PR: body carried |
|
@Birfy review request — PR #2112 at
The tip is frozen — no further pushes until the repository verdict arrives. |
Fixes #2111
Written-by: Dev-2
Owner: Dev-2
Reviewer: Reviewer
Claim-Id: fa1c3f8bd1d84364bc5baf29a0b87068
Branch: dev/2-schedules-read-scope
Base: a91701d
Tip SHA: fa6982a
What changed
Two faces of one defect: the schedules page never finishes loading, and the read behind it does not narrow to the caller.
The page stays on
Loading….core/src/client/index.jscallsscheduleswith no payload; the host's authz layer treatsschedulesas a bot-targeted call and asks for a session id the call never carries, so a machine that has ever had a member sign in answersbot/forbiddenevery time.core/src/client/schedules.jsswallowed that refusal in.catch(() => {})and retried every 30 seconds, soviewstayed null andSchedulesPage,BotSchedules,ScheduleSummaryandRoomSchedulesnever drew anything. Repro: open the schedules page.A read that does not check the actor.
core/src/host/schedules.js:640wasschedules: () => schedulesView(), andschedulesView()filtered only byknown(owner)— the refused-scope rules of#1321/#1336had never been applied to this view.schedulesis now a read endpoint likeprojects: any signed-in actor is allowed, and the rows narrow to that actor in the data layer.core/src/host/authz.js—schedulesleavesBOT_TARGETEDand answersALLOWon its own branch.startsWith('schedule-')keeps its session-id gate byte for byte, and so does the:419branch. Writes are untouched.core/src/host/schedules.js—schedules(actor)passes the actor down; both loops call the module's ownsourceOf(entry.sessionId, actor)next to the existingknown(owner), so an unenforced install still gets the full list, an enforced one gets the caller's rows, and an enforced one with no actor gets none. No parallel predicate was added.core/src/host/channel.js— hands the actor toschedulesand toschedule-pause|resume|retry|delete; only the read changes behaviour, the writes keep their gate.core/src/client/schedules.js— a refused or failed read now publishes a failure instead of being dropped, and the page drawsThe scheduled tasks could not be read: {reason}. A Bot's card says the same sentence and offers noNew task; the details line stays quiet.core/src/host/project-assistant.js— the digest create carries{ memberId: rt.botOf(botId)?.ownerId }; without it the fail-closed path rebuilds the digest on every reconcile.core/src/client/i18n.js,docs/subsystems/{team,core-host,core-client}.md,changelog.d/2111-schedules-read-scope.fixed.md— the new sentence in Chinese, and the docs now sayschedulesis a read any signed-in actor may make.Red first
Base
a91701d6, source reverted, the new tests in place — read three times, identical each run:core/test/schedules.test.js— 35 tests / 33 pass / 2 fail x3not ok 32 - with a member signed in, the scheduled-task list answers without a sessionId— the host answeredbot/forbidden.not ok 33 - the scheduled-task list carries only the caller's own rows— two owners each with a Bot; the test asserts the list holds the caller's own title only, and it held both.core/test/schedules-client.test.js— 41 / 38 / 3 x3:not ok 39 - a failed read is a sentence on the page, not Loading… for good,not ok 40 - a Bot's card says the read failed, and the details line stays quiet,not ok 41 - the failure sentence is null while there is nothing to say.core/test/project-assistant.test.js— 27 / 27 / 0 at base.with owner scoping on, project-create still records the digest it scheduledis a guard: green at base, red under mutation M3 below.Two more are guards rather than red-first evidence and are counted as such:
with nobody signed in, the scheduled-task list still answers the host's tasks(green at base, red under M0) and the nothing-to-say case above.After the fix, three times each:
schedules35/35/0,schedules-client41/41/0,project-assistant27/27/0.Mutations
Each on the final tip, reverted afterwards (
git status --porcelainempty):actor == null || sourceOf(...))schedules35/23/12 — the new guard fails plus 12 legacy tests, i.e. an unenforced local install would lose its listknown(owner)carries only the caller's own rowsschedulesback behindrequireSession(body?.sessionId)answers without a sessionIdproject-assistant26/1 — onlyrecords the digest it scheduledNo existing assertion was changed:
authz.test.js:231still holds, and no test in the tree asserted that a session-lessschedulescall is refused.Verification at the tip
Tip SHA:
fa6982aa0997334ac4626601d0a122d8d5881996Ran:
TZ=UTC ARCH_DOCS_BASE=a91701d6 npm run verifyon a clean, committed tree — exit 0:core2591 tests; the baseline counted the same way on a cleana91701d6tree is 2584, so this PR is +7plugins/social542 (541 pass, 1 skip:Cordis 4 not installed);plugins/tree127 (126 pass, 1 skip);plugins/timeline135 (134 pass, 1 skip);plugins/workflow337;.github/scripts598;interop37;live-e2e24 — all passarch-docs: OK (21 changed file(s))ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5),ok 10 patches apply cleanly in order,ok core/ equals ds-bot/ + patches/ds-bot/,ok patches/ds-bot/ is what generate writes (generate is a no-op)—generateran twice, the secondgit status --porcelainwas emptybuild-client --checkpassesNot run (and why): no browser or live-instance check of the schedules page, and no live multi-member install — the two-owner case is constructed in-process against the host's own handler.
live-e2eran to its usual 24 passing tests; nothing there touches schedules.Scope and limits
schedulesis opened up.schedule-create,schedule-update,schedule-pause,schedule-resume,schedule-retryandschedule-deletekeep their session-id gate; the:419branch is unchanged.schedulesView()did not consult the actor. Nothing here is a request recipe, and the tests build their two owners in-process.Checkpoint
{ "pr": 2112, "claim": "Dev-2", "issue": 2111, "summary": "The schedules page stayed on Loading for good, and the read behind it did not narrow to the caller. The client now sends the schedules read the way it sends projects, the host answers any signed-in actor and filters the rows through the module's own sourceOf, and a failed read draws a sentence instead of a spinner. Write endpoints keep their session-id gate. Tests cover the session-less read, two owners seeing only their own rows, and the unenforced local install keeping its full list.", "files": [ "core/src/host/authz.js", "core/src/host/schedules.js", "core/src/host/channel.js", "core/src/host/project-assistant.js", "core/src/client/schedules.js", "core/src/client/i18n.js", "core/test/schedules.test.js", "core/test/schedules-client.test.js", "core/test/project-assistant.test.js", "core/client.js", "docs/subsystems/team.md", "docs/subsystems/core-host.md" ], "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=a91701d6 npm run verify (clean tree)", "result": "green", "at": "fa6982aa0997334ac4626601d0a122d8d5881996" }, "next": "none — ready for review" }