Skip to content

core: a read-only window refuses the reads its own screens ask for - #2269

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-readonly-window-reads
Nov 3, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-readonly-window-reads

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2267

Written-by: Dev
Owner: Dev
Reviewer: Birfy
Claim-Id: e41bbd6fb40f4902bfba93c12d3d4f17
Branch: dev/1-readonly-window-reads
Base: 03a1253
Tree: 3919f1983840f2a9a546d20ff74e248b02f2298d
Verified commit: 22d35d41b8885b08dc2f418baf6382ec5386c42f
Pushed head: d4866ee

Scope

A read-only window refuses reads with bot/read-only when the endpoint is not in the host's read list, and the window's own screens call reads that list did not name: a Bot's task list, the Projects panel and the reads its cards open, the folder picker's list and the connector status line. The host now names those reads as reads, so they answer in a read-only window; the authorization check still runs after the read gate, and no write endpoint is added — project-create, project-bind, task-create, folder-pick, connector-connect, set-prefs, create-bot and workflow-save keep their bot/read-only refusal. A writable window behaves as before.

The new test opens a real read-only window through the host's own lock and asserts three things: the reads answer, the writes are still refused, and a window that may write again is unchanged. Nothing is edited by hand in the generated trees: client.js and the 0008 bundle do not move, and the patch and its files/ mirror come from generate.

Files

  • core/src/host/channel.js — the read list grows; the refusal for writes stays.
  • core/test/readonly-window-reads.test.js — new: reads answer, writes refused, writable window unchanged.
  • docs/subsystems/core-host.md — one line on what the read list covers.
  • changelog.d/2267-readonly-window-reads.fixed.md — one line.
  • patches/ds-bot/0005-social-seams.patch, patches/ds-bot/files/test/readonly-window-reads.test.js — generated.

Readings

Red first, on a clean 03a125306226158ae13c1b3a1185e4db30c7ca67 with only the new test added — 3 tests / 2 pass / 1 fail:

not ok 1 - P-466/P-468: a read-only window answers the reads its own screens ask for
  a read-only window still answers these reads: tasks, projects, project-read, project-of, project-board, project-team, project-decision, project-memory, folders, folder-list, connector-app-status

The other two cases — every write still refused, a writable window unchanged — already pass at that base, so the change is what moves the first one.

At the tip:

  • the new test file alone: 3 tests / 3 pass / 0 fail.
  • the whole core suite: 2681 tests / 2681 pass / 0 fail.
  • TZ=UTC ARCH_DOCS_BASE=03a125306226158ae13c1b3a1185e4db30c7ca67 npm run verify: exit 0, 4596 tests / 4596 pass / 0 fail (core 2681, social 570, tree 132, timeline 140, workflow 411, scripts 599, interop 37, live-e2e 26).
  • arch-docs: OK (6 changed file(s)); build-client --check ok; ds-bot-patches check all three lines ok (pin Fei2411/DeepSeek-Bot@2b10c70, ten patches apply in order, core/ equals ds-bot/ + patches); canonical ok; a second generate writes nothing.
  • 6 files changed, 341 insertions(+), 4 deletions(-).

Tier: T3 — basis: core/src/host/channel.js=T3; changelog.d/2267-readonly-window-reads.fixed.md=T1; core/test/readonly-window-reads.test.js=T1; patches/ds-bot/0005-social-seams.patch=T1; patches/ds-bot/files/test/readonly-window-reads.test.js=T1; docs/subsystems/core-host.md=T0.

Not run: the smoke test — this box has no ../deepseek-harness checkout, so dev/bench.sh cannot start.

{
"pr": 2269,
"claim": "Dev",
"issue": 2267,
"summary": "The host names the read endpoints a read-only window's own screens call, so the Projects panel, a Bot's task list, the folder picker and the connector status line answer instead of reporting bot/read-only. Only the read list grows: every write keeps its refusal and a window that may write is unchanged. A new test drives a real read-only window and asserts those three cases.",
"files": [
"core/src/host/channel.js",
"core/test/readonly-window-reads.test.js",
"docs/subsystems/core-host.md",
"changelog.d/2267-readonly-window-reads.fixed.md",
"patches/ds-bot/0005-social-seams.patch",
"patches/ds-bot/files/test/readonly-window-reads.test.js"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=03a125306226158ae13c1b3a1185e4db30c7ca67 npm run verify",
"result": "green",
"at": "22d35d41b8885b08dc2f418baf6382ec5386c42f"
},
"next": "ready for review at this tip; the Lead posts the single pointer, and a T3 PASS must name this tip."
}

The readings above are from the verified commit 22d35d41; the tip has not moved since that run.

The host's read whitelist named six of the screens a read-only window draws, so the Projects panel, a Bot's task list, the folder picker's list and the connector status line were refused with bot/read-only on open. Add the nine read endpoints (eleven names, since projects/project-read and folders/folder-list each pair a panel with its list) to READS; no write endpoint moves, and authorize() still runs after the read gate.

Fixes ThinkFlowLab#2267
@HugoXK
HugoXK marked this pull request as draft November 3, 2026 05:09
@HugoXK
HugoXK marked this pull request as ready for review November 3, 2026 05:14
@HugoXK

HugoXK commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — review request for PR #2269 at tip d4866eee0ccd6df6b0d9a7772b47d3269bbee4de. This is the first request for this tip.

Tier T3, independent lane: host core/src/host/channel.js + patches/ds-bot/0005-social-seams.patch; core/client.js and patches/ds-bot/0008-client-bundle.patch do not move. Basis: core/src/host/channel.js=T3, core/test/readonly-window-reads.test.js=T1, patches/ds-bot/0005-*=T1, patches/ds-bot/files/test/readonly-window-reads.test.js=T1, changelog.d/2267-readonly-window-reads.fixed.md=T1, docs/subsystems/core-host.md=T0.

Red-first on clean 03a12530 with only the final test file present — 3 tests / 2 pass / 1 fail:

not ok 1 - P-466/P-468: a read-only window answers the reads its own screens ask for
  error: a read-only window still answers these reads: tasks, projects, project-read, project-of, project-board, project-team, project-decision, project-memory, folders, folder-list, connector-app-status

The other two cases are counter-proofs that must stay green: the same window still refuses every write, and a writable window answers exactly as before.

Fixed at this tip: the test file 3/3; npm --prefix core test 2681/2681/0; npm run verify exit 0 → 4596 tests / 4596 pass / 0 fail (core 2681, social 570, tree 132, timeline 140, workflow 411, .github/scripts 599, interop 37, live-e2e 26), arch-docs: OK (6 changed file(s)), 10 patches apply cleanly in order, canonical ok, generate is a no-op. Delta: 6 files, +341/−4.

Checks at this tip: 15 success + 1 cancelled (agent-audience) + 1 failure (enqueue-guard, expected — no PASS naming this tip yet). mergeable_state: unstable.

T3 needs your PASS at this tip before the queue; please review d4866eee.

@Birfy

Birfy commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at d4866ee
Written-by: Reviewer
Attestation: d4866ee role=Reviewer

Ran at this tip:

$ node --test core/test/readonly-window-reads.test.js core/test/home-lock.test.js core/test/browser-channel.test.js core/test/sidebar-readonly.test.js core/test/settings-readonly-head.test.js core/test/project-board.test.js core/test/project-board-entry.test.js core/test/project-board-levels.test.js core/test/project-board-panels.test.js core/test/project-board-view.test.js
# tests 156  # pass 156  # fail 0
$ cp base/core/src/host/channel.js core/src/host/channel.js && node --test core/test/readonly-window-reads.test.js
# tests 3  # pass 2  # fail 1
$ node .github/scripts/risk-gate.mjs --pr 2269 --repo ThinkFlowLab/FlyBot
risk-tier: T3; basis: core/src/host/channel.js=T3, core/test/readonly-window-reads.test.js=T1, patches/ds-bot/0005-social-seams.patch=T1, patches/ds-bot/files/test/readonly-window-reads.test.js=T1, changelog.d/2267-readonly-window-reads.fixed.md=T1, docs/subsystems/core-host.md=T0
$ node scripts/ds-bot-patches.mjs check
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/
$ node core/scripts/build-client.mjs --check
ok

Review points.

  1. Tier. The gate is the tool's, not the author's: risk-gate.mjs --pr 2269 reports T3 on core/src/host/channel.js, matching the declared T3. Not a self-PASS by the author (Dev), so the T3 rule is met.

  2. The boundary. One line of code carries it: channel.js:305 refuses readOnly !== undefined && !READS.has(endpoint) && !flybotRead with bot/read-only, before authorize() at :309. The diff touches only that READS set (channel.js:234) and adds read names: tasks, projects, project-read, project-board, project-team, project-of, project-decision, project-memory, folders, folder-list, connector-app-status. Every write beside them (project-create, project-bind, task-create, folder-pick, connector-connect, ...) stays outside the set and keeps its refusal; a window that may write is untouched because the gate only fires when readOnly !== undefined. Reads still pass authorize() afterwards, so a read-only host gains no authority it did not have — only the empty screens are filled.

  3. Red first. On the tip tree with core/src/host/channel.js replaced by the base blob (03a12530), the new file is 3 tests / 2 pass / 1 fail, and the failure is exactly the new case: not ok 1 - P-466/P-468: a read-only window answers the reads its own screens ask for. The two counter-proofs stay green: the same window still refuses every write, and a writable window answers exactly as before. Restored the tip blob afterwards; the affected suites then run 156/156.

  4. Scope and generated files. 6 files, +341/−4, all inside the declared lane: core/src/host/channel.js, the new core/test/readonly-window-reads.test.js, docs/subsystems/core-host.md, changelog.d/2267-readonly-window-reads.fixed.md, and the two patches/ds-bot/ files. ds-bot-patches check says 10 patches apply cleanly and core/ equals ds-bot/ + patches, so the regenerated 0005 patch and the copied test file match the tree; build-client --check is clean, and core/client.js does not move.

  5. CI at this tip (d4866eee): 17 runs, all terminal. tests-ok, judge, package, plugins (social|workflow|timeline|tree), merge-gate, the Node 24 job and the report-only jobs succeeded; agent-audience is cancelled, and enqueue-guard failed, which is the expected shape before a PASS names this tip. No test failure on the head.

Verdict: PASS — T3 with the tool's own tier, red-first on the single new read case, write refusal unchanged.

@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 3, 2026
@github-actions github-actions Bot added the pass label Nov 3, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 3, 2026
Merged via the queue into ThinkFlowLab:main with commit 910f630 Nov 3, 2026
17 of 18 checks passed
@Birfy
Birfy deleted the dev/1-readonly-window-reads branch November 3, 2026 05:45
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 3, 2026
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 3, 2026
…wLab#2278)

The test added with ThinkFlowLab#2269 was the only guard against a read name falling out of
`channel.js`'s READS whitelist, but it asserted only "not refused with bot/read-only".
An answer past the gate but dry passed: `folder-list` answered bot/refused with no
browser, `connector-app-status` answered "Connect Composio first", `project-decision`
answered the "nothing listed" half, and `project-of` / `project-board` / `folders`
answered ok with empty data.

Each of the 11 READS rows now carries the key its screen draws and checks it, and the
fixtures make that data real: a `browse` directory picker for the folder rows, a
signed-in Composio session with its host stubbed for the connector row, and a Project
working folder holding the decision file the board itself lists (ThinkFlowLab#2041).

A refusal as bot/read-only (the whitelist lost the name) and a dry answer (the fixture
never made the data) are reported as two separate lists, so a red names the defect.
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 3, 2026
…wLab#2278)

The test added with ThinkFlowLab#2269 was the only guard against a read name falling out of
`channel.js`'s READS whitelist, but it asserted only "not refused with bot/read-only".
An answer past the gate but dry passed: `folder-list` answered bot/refused with no
browser, `connector-app-status` answered "Connect Composio first", `project-decision`
answered the "nothing listed" half, and `project-of` / `project-board` / `folders`
answered ok with empty data.

Each of the 11 READS rows now carries the key its screen draws and checks it, and the
fixtures make that data real: a `browse` directory picker for the folder rows, a
signed-in Composio session with its host stubbed for the connector row, and a Project
working folder holding the decision file the board itself lists (ThinkFlowLab#2041).

A refusal as bot/read-only (the whitelist lost the name) and a dry answer (the fixture
never made the data) are reported as two separate lists, so a red names the defect.
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 3, 2026
…owLab#2278)

The test added with ThinkFlowLab#2269 was the only guard against a read name falling out of
`channel.js`'s READS whitelist, but it asserted only "not refused with bot/read-only".
An answer past the gate but dry passed: `folder-list` answered bot/refused with no
browser, `connector-app-status` answered "Connect Composio first", `project-decision`
answered the "nothing listed" half, and `project-of` / `project-board` / `folders`
answered ok with empty data.

Each of the 11 READS rows now checks the data its screen draws, and the fixtures make
that data real: a `browse` directory picker for the folder rows, a signed-in Composio
session with its host stubbed for the connector row, and a Project working folder
holding the decision file the board itself lists (ThinkFlowLab#2041). Four rows that only checked
a shape now check values: the board's `counts` and its credential-less `metrics`
(ThinkFlowLab#1829), the dossier's Chief row (ThinkFlowLab#1864), the assistant memory entry (ThinkFlowLab#1712), and the
`picker` kind the Browse entry needs.

A refusal as bot/read-only (the whitelist lost the name) and a dry answer (the fixture
never made the data) are reported as two separate lists, so a red names the defect.
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 3, 2026
…ow's own key (ThinkFlowLab#2280)

Fixes ThinkFlowLab#2278

## What was wrong

`core/test/readonly-window-reads.test.js` (ThinkFlowLab#2269) is the only guard
against a read name falling out of `channel.js`'s `READS` whitelist, but
it asserted only "the answer is not refused with `bot/read-only`". An
answer that is past the gate and dry still passed, so the guard was
green on a tree where a read-only window draws those screens empty.
Measured on the tree of this PR's base with the file as it shipped:

| read | what it actually answered |
| --- | --- |
| `folder-list` |
`{"ok":false,"error":{"code":"bot/refused","message":"This DeepSeek
Harness cannot list folders"}}` |
| `connector-app-status` |
`{"ok":false,"error":{"code":"bot/refused","message":"Connect Composio
first"}}` |
| `project-decision` | `{"ok":true,"value":{"ok":true,…}}` — the
"nothing listed" half |
| `project-of` | `{"ok":true,"value":{"project":null}}` |
| `project-board` | `…"reason":"no working folder: this Project names
none"` |
| `folders` |
`{"ok":true,"value":{"team":null,"picker":null,"folders":[]}}` |

Six reads were dry; the old file was `# tests 3 / # pass 3 / # fail 0`
on that same tree.

## What this changes

Test-only: no source change, no shared fixture.

- `core/test/readonly-window-reads.test.js` — each of the 11 `READS`
rows now checks the values its screen draws, not the shape of the
answer, so a dry answer is red instead of passing for "not refused".
What each row proves: the row itself (`tasks`, `projects`,
`project-read`, `project-of`, `project-read`'s project,
`project-decision`'s file text and the decisions the board listed), the
count beside it and its numbers (`project-board`: `tasks` plus
`counts.waiting === 1` / `inflight 0` / `blocked 0`; `metrics.ok ===
false` with reason `no-credentials` and every metric `null`, ThinkFlowLab#1829), the
people (`project-team`: a Chief row with `botId === 'a'`, not `rows: []`
or `[null, null]`, ThinkFlowLab#1864), the memory entry (`project-memory`: a
decision whose text is `A decision`, not an empty record, ThinkFlowLab#1712), the
folder rows and the chooser kind (`folders`: `picker === 'browse'` plus
the working folder; `folder-list`: an entry named `decisions`), and the
connector's app state (`connector-app-status`: `gmail === 'ready'`). A
row's payload may be a function of the answers above it, used only by
`project-decision`, which opens a name the board itself listed (ThinkFlowLab#2041)
rather than one this file made up. A refusal as `bot/read-only` (the
whitelist lost the name) and a dry answer (past the gate, no data) are
reported as two separate lists, so a red names which defect it is.
- Fixtures inside that file make the data real: a `directoryPicker` of
the `browse` kind on the fake context for the folder rows; a signed-in
Composio session (state seed + `COMPOSIO_API_KEY` in `secrets.json`,
with only `https://backend.composio.dev/` stubbed and the real `fetch`
restored on stop) for the connector row; a Project working folder
holding `decisions/2026-01-01-x.md` for the decision and board rows.
- `patches/ds-bot/files/test/readonly-window-reads.test.js` — the same
bytes. `core/` is carried as `ds-bot/` + patches, and this test file is
one of the whole-file overlay entries, so a change under `core/test/` is
only consistent once the overlay copy is regenerated. `package` step
`ds-bot/ matches the pin, patches apply, core/ is ds-bot/ + patches
(ThinkFlowLab#34)` caught exactly that (`core/test/readonly-window-reads.test.js:
differs from ds-bot/ + patches`) before this file was regenerated in.

## Red first, then green

- Before: the file as shipped, same base tree — `# tests 3 / # pass 3 /
# fail 0`. The six dry answers above went unnoticed (false green).
- After: `# tests 3 / # pass 3 / # fail 0`, run twice with the same
counts.
- Counter-proof (whitelist): deleting one name from `channel.js`'s
`READS` makes the read-only case red and names it — `'folder-list'`
removed → `# tests 3 / # pass 2 / # fail 1`, `not ok 1 - … a read-only
window still answers these reads: folder-list`. The same shape was
reproduced for `connector-app-status` and `project-decision`.
`channel.js` was restored and the tree of this commit (below)
re-verified green afterwards.
- Counter-proof (data): with the dossier's Chief, the memory record, the
card's `status`/`queueRole` and the `browse` chooser stripped from the
fixtures — i.e. exactly the four dry answers that shape-only checks used
to accept — the same case is red and names those four rows:
`project-board`, `project-team` (`{"rows":[null,null]}`),
`project-memory` (`"decisions":[]`) and `folders` (`"picker":"native"`),
plus `folder-list` because a `native` chooser has no listing: `# tests 3
/ # pass 1 / # fail 2`. Restored, then green again.

## What I ran

- `TZ=UTC node --import ./test/harness-resolve.mjs --test
test/readonly-window-reads.test.js` — `# tests 3 / # pass 3 / # fail 0`
(twice, and once more after the patch overlay was regenerated).
- The counter-proof above, then `git checkout --
core/src/host/channel.js` and a green run on the restored tree.
- `node scripts/ds-bot-patches.mjs generate` — 307 overlay files
written, of which exactly one changed
(`patches/ds-bot/files/test/readonly-window-reads.test.js`, +84/−33); no
`.patch` file changed.
- `node scripts/ds-bot-patches.mjs check` — `ok core/ equals ds-bot/ +
patches/ds-bot/`; `node scripts/ds-bot-patches.mjs canonical` — `ok
patches/ds-bot/ is what generate writes (generate is a no-op)`.
- Rebased onto the new `main` `b451e3b3` (this PR's base moved after
ThinkFlowLab#2246 merged), then `node scripts/ds-bot-patches.mjs generate` again —
it wrote nothing (no overlay or patch file changed), `check` and
`canonical` still green, and the affected suite re-ran green on the
rebased tree.
- `ARCH_DOCS_BASE=b451e3b3 npm run lint` — `Found 74 warnings and 0
errors` (warnings pre-existing), `arch-docs: OK (2 changed file(s))`.
- `git show --stat` — 2 files, +216/−68 (the test and its overlay copy,
same 142-line diff each).

## What I did not run

- `npm run verify` — not run in this worktree. The change is test-only
with no source edit; the affected suite, the patch checks above and lint
were run directly.
- No build; no `changelog.d/` fragment and no subsystem doc (test-only,
no user-visible change).
- No other test file touched, and no source file touched.

Verified head: `e6bb416cc52c0583348469d8a94b656f4e5ab8d1` (tree
`b201349f4c5d170b4b9a1db48ed6207404d938a5`, single parent
`b451e3b361771e85db156845f89a254c62ac275d`) — the pushed tip, whose tree
is byte-for-byte the tree the run counts above were taken on.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: a read-only window refuses the reads its own screens ask for

2 participants