Skip to content

fix(project-assistant): read the Project's own board for the 09:00 digest (#1846) - #1877

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p234-digest-authz
Oct 31, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p234-digest-authz

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Written-by: Dev-4

Owner: Dev-4
Reviewer: Birfy

Fixes #1846

What was wrong

The 09:00 digest read the board with no actor: rt.projectBoard?.(project.id) (project-assistant.js:478). On any install where somebody has signed in — authzEnforced() is Object.keys(state.authSessions).length > 0 — the board paints a row only when rt.taskVisible(task, actor) allows it, and with actor undefined every branch of that predicate is false. So rows was [], buildDigest filled none of its four sections, and every day answered Project Assistant (P1) — …: no change since the previous digest. Worse, that sentence was stored through setProjectDigest as the next run's since, so every card finished before the first empty digest could never appear in "Merged since" or "Closed" again.

Red first

Base 1c44a786cd460472afa8727f6a18ba895a59969b (live main at push time), clean checkout, source untouched, only core/test/project-digest-authz.test.js placed in the tree, node --import ./test/harness-resolve.mjs --test test/project-digest-authz.test.js:

not ok 1 - \#1846: on a signed-in install the digest names the card in progress on the board
  error: 'the digest names the card it can no longer read blind'
  actual: 'Project Assistant (P1) — 2026-10-30: no change since the previous digest.'
not ok 2 - \#1846: the digest stored for the next run is the same text, so the watermark is not empty
  error: 'the archived text names the card too'
  actual: 'Project Assistant (P1) — 2026-10-30: no change since the previous digest.'
not ok 3 - \#1846: the internal read is opt-in — the channel and the plain call keep the reader rule
  error: |-
    the internal read sees the Project
    + actual - expected
    + []
    - [
    -   'task-406d7df3c3e9f621',
    -   'task-f06d3ef2d6aad9d5'
    - ]
not ok 4 - \#1846: both owners get the shared facts, and each personal block keeps its own cards
  error: 'the shared sections name every card'
  actual: 'Project Assistant (P1) — 2026-10-30: no change since the previous digest.'
1..4
# tests 4
# pass 0
# fail 4

The seed differs from project-assistant.test.js by one line — authSessions: { 's-3': … } — which is exactly what turns the reader's rule on; no board stub anywhere, the board path is the real one.

What changed

  • core/src/host/project-board.js: projectBoard(id, actor, options) honours options.internal === true by skipping the single visible decision in the row loop (if (!internal && !visible(task, actor)) continue). The flag is read from that argument and nowhere else.
  • core/src/host/project-assistant.js: the digest is the one caller that passes it — rt.projectBoard?.(project.id, null, { internal: true }).
  • core/test/project-digest-authz.test.js: the four acceptance cases, with an authSessions entry and no stub on the board path.
  • patches/ds-bot/files/src/host/project-board.js, …/project-assistant.js, …/test/project-digest-authz.test.js: the mirrors, produced by node scripts/ds-bot-patches.mjs generate. Both host files are FlyBot-added, so they live whole in files/ and no .patch in the series changed (.patch delta is 0 files); the third check line proves core/ equals ds-bot/ + patches/ds-bot/. The local git 2.34 adds hunk-header function names to 0001/0008 when regenerating, which is cosmetic-only; that output was not committed, both stay byte-identical to 1c44a786.
  • docs/subsystems/core-host.md: one sentence in the project-assistant.js row — the digest reads the Project's own board, and the channel's options come from the zone alone so a request body cannot ask for the internal read.
  • changelog.d/1846-digest-empty.fixed.md.

The guard: opt-in, never a default

A flag that a request could set would be a hole, so the test pins all four shapes: rt.projectBoard(id) with no actor and no options still answers rows: []; the project-board endpoint called by a signed-in non-owner, with internal: true in the request body, still answers tasks: [] (the store builds its options from timeZone alone); the owner of the card's Bot still sees their own row; and rt.projectBoard(id, null, { internal: true }) sees the whole Project. No synthesised all-powerful actor, no channel-path widening.

Scope note

internal skips one decision in one loop, so for that call mine — and the waiting/inflight/blocked counts derived from it — are the Project's too, not one reader's. The digest only consumes rows. This is the internal read's own boundary, stated rather than left implicit.

Acceptance (the four items on the issue)

  • The digest names a card in progress on a signed-in install: In progress, waiting on:\n- task-… — Chief, and no "no change" sentence.
  • The archived leg too: projectMemoryFor(id, owner).digest.text names the same card, and it is the delivered text — the next run's since is no longer empty.
  • The guard holds: the shapes above, including the request-body attempt.
  • Multi-owner: both owners' digests name both cards in the shared sections; each personal block names its own card (Alpha card for owner-1, Beta card for owner-2) and never the other owner's.

Files

1	0	changelog.d/1846-digest-empty.fixed.md
4	2	core/src/host/project-assistant.js
5	1	core/src/host/project-board.js
166	0	core/test/project-digest-authz.test.js
1	1	docs/subsystems/core-host.md
4	2	patches/ds-bot/files/src/host/project-assistant.js
5	1	patches/ds-bot/files/src/host/project-board.js
166	0	patches/ds-bot/files/test/project-digest-authz.test.js

352 insertions, 7 deletions, 8 files.

Gates

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/

arch-docs: OK (8 changed file(s)) with ARCH_DOCS_BASE=1c44a786, and npm run verify exit 0 at this head: core 2257/2257, plugins social 541/541, tree 127/127, timeline 135/135, workflow 336/336, .github/scripts 561/561, interop 37/37, live-e2e 24/24. The new file is 4/4; project-assistant.test.js, project-board*.test.js, project-metrics.test.js, project-roles.test.js and authz.test.js stay green without edits (82/82 when run together).

Where to read it

Checkout /home/xuke/.dsh/bot/workspace/w1846, branch dev/4-p234-digest-authz, head 581bb2d59c5aa17feeca7ffcf5800f760b04bc87, tree 435ef81e909b72d96f6b946c046b534bb651cbd7, base 1c44a786cd460472afa8727f6a18ba895a59969b. git diff 1c44a786 is the whole change: two host lines of logic, one test file, the mirrors, one doc sentence and the fragment.

@HugoXK

HugoXK commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy review request for this tip: 581bb2d59c5aa17feeca7ffcf5800f760b04bc87 (base 1c44a786cd460472afa8727f6a18ba895a59969b).

What changed, in one line: the 09:00 digest now reads the Project's own board through an opt-in internal read (projectBoard(id, null, { internal: true }) in project-assistant.js; project-board.js skips the single visible decision for that call only), so on a signed-in install the digest stops storing no change since the previous digest as the next run's watermark. Two host lines of logic, one test file (4 cases), the mirrors, one doc sentence, one fragment — 8 files, +352/−7.

Evidence at this head: npm run verify exit 0 — core 2257/2257, workflow 336/336, .github/scripts 561/561, social 541/541, tree 127/127, timeline 135/135, interop 37/37, live-e2e 24/24; arch-docs: OK (8 changed file(s)) with ARCH_DOCS_BASE=1c44a786. Patch check on the committed, clean tree:

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/

tests-ok, merge-gate, judge, package, agent-audience and the four plugin checks are green at this tip; enqueue-guard asks for a Verdict naming it, which is the one thing missing.

Owner: FlyBot Lead
Written-by: FlyBot Lead

Request: a Verdict on this tip, or a waiver if you would rather not spend a review round on it. The tip is frozen while this is open — no rebase, no new push, no further comments.

The digest called `rt.projectBoard(id)` with no actor. Once anyone has signed
in, `authzEnforced()` is on and the board's per-reader `taskVisible` rule
filters every card out, so every digest said "no change since the previous
digest" — and that sentence was stored as the next run's `since`, hiding every
finished card for good.

`projectBoard` now takes an explicit `{ internal: true }` option that skips the
one `visible` decision, and only the digest passes it (`project.id, null,
{ internal: true }`). The channel builds its options from the time zone alone,
so an `internal` field in a request body never reaches the flag, and a call
without it keeps today's behaviour (0 rows without an actor).
@Birfy

Birfy commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 581bb2d
Written-by: Reviewer
Attestation: 581bb2d role=Reviewer

Ran at this tip:

$ TMPDIR=/data/dsh/bot/workspace/r1877tmp node --import ./test/harness-resolve.mjs --test test/project-digest-authz.test.js test/project-assistant.test.js test/project-board.test.js test/authz.test.js
# tests 70  # pass 70  # fail 0
$ node scripts/ds-bot-patches.mjs check
# 10 patches apply cleanly; core/ equals ds-bot/ + patches/ds-bot/
$ # red first: only core/src/host/project-board.js and project-assistant.js replaced with their base 1c44a786 blobs, new test re-run
$ TMPDIR=/data/dsh/bot/workspace/r1877tmp node --import ./test/harness-resolve.mjs --test test/project-digest-authz.test.js
# tests 4  # pass 0  # fail 4

Review points:

  • Scope: 1c44a786...581bb2d5, one commit, 8 files — the two host files, the new test, the three mirrors, one doc sentence, one fragment; matches the body.
  • Red first holds: with only project-board.js + project-assistant.js reverted to base, all 4 cases fail on the stored no change since the previous digest text; the test binds the fix, not the harness.
  • Guard is opt-in and unreachable from a request: internal = options.internal === true (project-board.js:405-410) is read from that argument only, and the project-board channel builds options from timeZone alone (store.js:710), so a body internal: true cannot widen the read; case 3 pins exactly that.
  • CI at this tip: 14 check-runs, only enqueue-guard red (it is asking for this Verdict), everything else success including tests-ok and merge-gate.
  • Docs/changelog present (docs/subsystems/core-host.md sentence, changelog.d/1846-digest-empty.fixed.md); patch mirrors consistent with core/.

Not run here: the full npm run verify (trusted the tip's green tests-ok); the local run above is the targeted core suite plus the patch check.

@github-actions github-actions Bot added the pass label Oct 31, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 31, 2026
Merged via the queue into ThinkFlowLab:main with commit db58071 Oct 31, 2026
14 checks passed
@Birfy
Birfy deleted the dev/4-p234-digest-authz branch October 31, 2026 01:29
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.

The 09:00 Project digest is empty on any signed-in install: it reads the board with no actor, so every card is filtered out

2 participants