Skip to content

Stage 4.2: per-user personalized 09:00 digests with quiet, opt-out and authorization controls (#1550) #1733

Closed

Description

@Birfy

Written-by: Chief
Part of #1550, fourth slice of Stage 4. Runs under the owner's 2026-10-29 instruction to carry #1550 through Stages 1-4, and under the identity decision recorded on #1712.

Depends on

2.5 (#1712, merged d34e4e62, shipped in v0.2.14): the Project Assistant, its private FlyBot-side memory, one 09:00 Asia/Shanghai schedule per assistant Session, and the identity rule — a single-install owner's GitHub identity is the host connector account; an unsigned-in connector, or a multi-user host whose ownerUserId is not the host user, refuses and escalates needs-human, and never falls back to the shared App.

3.1 (#1720, merged in 80709a71): the typed task record (taskClass, reworkOf, handoff, evidence, lease) this slice reads when it prints actionable items.

Why

Stage 4's acceptance test is a comparison, not a delivery: two users receive different personalized digests derived from the same factual project status without revealing one another's private memory. Today one assistant serves one owner; nothing states what happens when two assistants live on one host, nothing lets a user hold or silence their own 09:00 digest, and no test proves one assistant cannot read the other's memory. This slice builds exactly that boundary and the controls around it.

What to build

  1. One digest per (project, owner), from one status read. Both assistants render from a single read of the same project status (the 2.4 reader and the 2.3 board read model — do not add a second reader). A test asserts the two digests quote the same underlying facts and different personalization, and that digest A carries no line originating in digest B's private memory.
  2. Quiet hours per user. A quiet window in the user's own timezone suppresses delivery, not generation: a digest due inside the window is held and delivered once when the window ends, labelled as held; a window that covers 09:00 means no 09:00 message at all, and that is the expected outcome, not a failure.
  3. Opt-out and authorization. A user can turn their digest off — no generation and no schedule wake-up — and back on; the state lives with the assistant's own record, not in a global setting. An assistant reads only the project it is bound to plus its own owner's memory; a request for another user's memory is refused with the boundary named, and a test proves the refusal.
  4. Actionable decisions block. Each digest lists at most N items (one constant) that need that user's decision, each with its task id and a one-line ask; an item the user has no authority over never appears.
  5. Failure isolation. One user's render or delivery failing does not stop the other: both 09:00 digests still go out, and the failure is recorded in the digest's evidence instead of being swallowed.

Acceptance

  • Tests in core/test/: two owners on one host get two digests from one status read with no private-memory bleed (both directions); a quiet window suppresses and then delivers once, labelled; a window covering 09:00 delivers nothing; opt-out stops generation and schedule wake-up and re-enabling restores it; a cross-owner memory read is refused naming the boundary; a failing render for user A still delivers user B's digest and records A's failure.
  • No second project-status reader, no second store; npm run verify green at the tip with TZ=UTC; core/client.js and patches/ds-bot/ regenerated from source only; docs/subsystems/core-host.md updated (or a valid Docs-not-needed: in both the branch commit and the PR body).
  • PR body: Part of #1550, Fixes #<this issue>, Owner: Chief, Reviewer: Reviewer, checkpoint block with claim: Chief.

Out of scope

Stage 4.1 meetings (#1731) and every Stage 5 research question. No new UI page: these controls ship on the existing Project Assistant settings and the digest record, not as a new board or a new scheduler.

Open question for the owner (only if it blocks the build)

If two maintainers arrive with no GitHub identity resolvable under the 2.5 rule, the slice still refuses and escalates needs-human — that behaviour stands and does not need a new decision. Only a request for a delivery channel other than the owner's own Session would need one; it is not in scope here.

Activity

  1. Birfy commented on Oct 29, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    Scope for the implementation node — Stage 4.2 slice of #1550 (#1733).

    Gap today. digest() in core/src/host/project-assistant.js serves one owner: memory is keyed by Project id only (projectMemoryOf / setProjectDigest in core/src/host/store.js), and the whole digest is gated on identity(), which also blocks in-app delivery. Quiet hours (core/src/shared/quiet-hours.js) only refuse a new/edited schedule (#1166) and never hold a due digest; no opt-out exists anywhere.

    Build.

    1. Memory and digest per (project, owner): one rt.projectBoard(project.id) read plus one listProjectState(project.workDir) read, two renders. Digest A must carry no line originating in B's memory (both directions); a cross-owner memory read is refused naming the boundary. No second project-status reader, no second store.
    2. Quiet window per owner, stored on the assistant record (not state.prefs): a due digest is held and delivered once when the window ends, labelled held; a window covering 09:00 delivers nothing (expected outcome, not a failure). Generation may proceed while delivery waits.
    3. Opt-out per owner on the assistant record: off means no generation and no schedule wake-up (pause/remove the 09:00 task through rt.scheduleEndpoint); on restores both.
    4. Actionable block: at most N items (one exported constant) that need that owner's decision, each with task id and a one-line ask; an item the owner has no authority over never appears.
    5. Failure isolation: A's render/delivery failure is recorded in A's digest evidence; B's 09:00 digest still goes out.
    6. Identity: in-app delivery to each owner's own Session must not depend on the 2.5 GitHub identity; the 2.5 refusal (no connector signed in, or a multi-member host without per-user linkage) stays for GitHub writes and still escalates.

    Files. core/src/host/project-assistant.js (main), core/src/host/store.js (owner dimension in the memory/digest record), core/src/host/schedules.js (opt-out path), core/test/project-assistant.test.js and core/test/quiet-hours.test.js (or one new core/test/project-digest-owners.test.js), docs/subsystems/core-host.md. Leave core/src/shared/** untouched: touching it is T3, while the host-only change is T2.

    Tests (red first, TZ=UTC). Two owners on one host: two digests from one status read, same underlying facts/different personalization, no private-memory bleed in either direction; a quiet window suppresses then delivers once labelled held; a window covering 09:00 delivers nothing; opt-out stops generation and schedule wake-up and re-enabling restores both; a cross-owner memory read is refused naming the boundary; A's render throws while B's digest is still delivered and A's failure is recorded.

    Before the PR. Branch from the live origin/main: open PR #1727 touches core/src/host/schedules.js, core/src/host/store.js and docs/subsystems/core-host.md, so a stale base will conflict. npm run verify with TZ=UTC; regenerate patches/ds-bot/ and core/client.js from source only; PR body carries Part of #1550, Fixes #1733, Owner: Chief, Reviewer: Reviewer and the checkpoint block with claim: Chief.

    Risk: host code (core/src/host/**) is T2 by .github/agent/risk-rules.json; keeping the change host-side keeps it T2.

  2. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 29, 2026
  3. self-assigned this
    on Oct 29, 2026
  4. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 29, 2026
  5. Birfy commented on Oct 29, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev

    Claimed #1733 — first step: per-(project, owner) memory in the store, then the two-owner digest test. Branch: dev/1733-per-owner-digest.

  6. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 29, 2026
  7. added a commit that references this issue on Oct 29, 2026
    e07239b
  8. removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:automationScheduled tasks and automationenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions