Skip to content

board-snapshot: once the open set completes, every run spends its whole budget on the closed history and never re-reads the live board — cards created or updated since 2026-09-10 are in no snapshot #18045

Description

@os-project-manager

Maintainer direct dispatch — the maintainer (pm@objectstack.ai) in the skills seat's chat session at 2026-09-13T14:58Z: 「现在就派发处理 备份缺口」. Filed and claimed by the skills seat (session session_01DAcomhvR9kKizeYgg89Vo8) under the direct-dispatch channel; the claim comment below carries the pair.

Symptom (manifest of the 2026-09-13T14:22Z scheduled run, board-archive tip b7c5f578)

Contract violated

The workflow's own purpose line: 「the archive answers exactly one question, after a loss: what did the record say?」 On 2026-09-13 a loss happened (an account suspension hid 12 cards and ~30 comments) and the record said nothing, because the walk plan (selectWalkPlan, phases open → history → incremental) only reaches incremental after history completes, and resume.since wins over everything while it is incomplete. Predecessor #17434 made the FIRST walk open-first; it did not cover the steady state after the open set completes.

Fix shape (the dev falsifies each premise first)

  1. Delta first, every run: before the history backfill, walk state=all&sort=updated&direction=asc&since=<previous manifest's generated_at, minus a small skew> and archive every row (open or closed) — bounded to a declared slice of the 800 budget (e.g. up to 300 requests, or until the delta is exhausted), so a busy board cannot starve the backfill entirely and a quiet one costs a page.
  2. History with the remainder: the existing backfill continues from its cursor with whatever budget is left; its cursor semantics unchanged.
  3. Manifest: record both cursors (delta.since / delta.complete and the history cursor); next_since becomes the delta's own high-water mark; board.read_at and the count check read the board's open count each run so count_check stops being pending.
  4. Self-test: the regression pinned — a manifest in history phase plus a card updated after generated_at ⇒ that card is archived on the very next run; the budget split; delta-then-history ordering; resume semantics unchanged for an interrupted history walk.
  5. ⛔ Constraints kept from the file's docblock: no second token, no retry loop, budget stays 800, GITHUB_TOKEN only; ⛔ no .claude/**; the cron and the workflow file stay as they are unless a premise forces a change (declare it if so).

File surface: scripts/pm/board-snapshot.mjs only (its self-test lives in the same file). Acceptance: node scripts/pm/board-snapshot.mjs --self-test green with the regression case; the derived gate families (dispatch-gates --commands) green; the PR body states the budget split and the new manifest keys.

Dedupe keywords: board-snapshot, open set, incremental, since, history backfill, budget, #17434.

Activity

  1. claude commented on Sep 13, 2026

    @claude
    Contributor

    Claim: PM loop round 1
    Session: session_01DAcomhvR9kKizeYgg89Vo8 (GitHub os-project-manager, skills seat), claimed at 2026-09-13T14:59Z
    Branch: claude/issue-18045-board-snapshot-delta-first
    Worktree: objectstack-issue-18045
    Domain: domain:skills (non-gate scripts/pm/**; NOT a governed path ⇒ in-seat contract-tier review, then ready + auto-merge by this seat). Channel: maintainer direct dispatch — pm@objectstack.ai in this seat's chat session at 2026-09-13T14:58Z: 「现在就派发处理 备份缺口」; the pair (pm:dispatched + assignee) was written at creation and read back.
    File surface: scripts/pm/board-snapshot.mjs ONLY (self-test in the same file): a bounded delta walk (state=all, since = the previous manifest's generated_at minus skew) runs FIRST every run, the history backfill takes the remaining budget, the manifest carries both cursors and reads the board's open count. ⛔ no second token, no retry loop, budget stays 800; ⛔ no .claude/**; the workflow file only if a premise forces it (declared).
    Container & model: M, mode:subagent, model: opus (default tier — no path mandate); seat review at the contract-review tier
    Clause-②: no
    Thread-read: none — the card is new; its body is the whole thread at this claim
    Serial constraints cleared at 2026-09-13T14:58Z: no branch or open PR names the archiver (git ls-remote for *snapshot* / *archiv* empty; the dev re-reads open PR file lists before editing); last patrol run on the anchor 2026-09-13T13:46Z named no hold touching this file; verify lock free; rate 14786/15000; origin/main at 08a363aa. Devs in flight after this one: 1 (the seat now caps concurrency at 2).
    Dedupe: #17434 (closed — the first-walk open-first fix, predecessor) is the only neighbour; no open card.


    Generated by Claude Code

  2. claude commented on Sep 13, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 18045,
      "status": "done",
      "branch": "claude/issue-18045-board-snapshot-delta-first",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18054",
      "premise_still_valid": true,
      "summary": "A bounded DELTA walk now runs first on every run of scripts/pm/board-snapshot.mjs once the open set is complete: state=all&sort=updated&direction=asc&since=... , archiving every row it sees (open or closed, issues and pull requests), capped at DELTA_REQUEST_SLICE = 300 of the 800-request budget. The history backfill then continues from its own cursor with the remaining 500 and its resume semantics are untouched; a spent slice is not a run stop. The manifest gains walk.delta = { complete, cursor, since, slice } beside walk.history, next_since becomes the delta's high-water mark instead of the backfill's August cursor, resume.phase now names the WALK that stopped, and the board's own open count is bought right after the delta so count_check stops being permanently `pending` in the history phase. One file changed, +588/-34. The workflow, the cron, the 800 budget, the single GITHUB_TOKEN and the no-retry rule are all unchanged. assignee on the card was os-project-manager at pickup (the PM's dispatch pair) and this run never wrote it.",
      "tests": "All measurements at final head c2a54670 (origin/main 226970bb merged in; the changed file's blob f3723530 is identical before and after that merge). (1) node scripts/pm/board-snapshot.mjs --self-test :: exit 0 -- 89 cases across 8 batteries BEFORE, 125 across 10 AFTER ('OK board-snapshot self-test: 125 cases pass across 10 batteries'). Two new batteries: 'the delta plan: which runs re-read the live board, and from where' (16) and 'the delta walk, driven: the archive catches up with the live board' (18); the census/count-check battery floor moves 15 to 17 and the roster floor 8 to 10. (2) NEW OFFLINE HARNESS: the card's premise that --self-test already drives the walk with an injected fetch is FALSE -- the old cases only exercised pure functions and snapshot() had no instrument at all. driveSnapshot()/fakeBoard() now run snapshot() end to end against an injected globalThis.fetch (no network, no token, restored in a finally, the module-global request counter zeroed and restored). Driven scenarios: the measured b7c5f578 manifest + a 900-row closed backlog (regression, order, count check), a delta larger than its slice (budget split), a history walk interrupted at max-requests=6 (resume unchanged), and three runs over a still board (idempotence). (3) ABLATION: deleted the 25-line delta-first step from snapshot(). On-disk proof -- markers 'THE DELTA, and it goes first' and 'budget.slice(deltaPlan.slice)' went 1 -> 0, git diff --stat showed 25 deletions, blob f3723530 -> e0cdbd7b. Result: 10 of 125 cases RED, THE REGRESSION among them, self-test exit 1. Restored with git checkout HEAD -- path under an EXIT/INT/TERM trap; restored blob f3723530 == HEAD blob, git diff HEAD empty, git status clean, self-test back to 125 green. FIRST ABLATION ATTEMPT WAS A PHANTOM: over a four-row board the backfill reaches 'today' inside one page, so THE REGRESSION case stayed GREEN on the ablated tree. The 900-row backlog was added for exactly that (its own commit c8c7d068's parent 44a90e10) and the case then went red. (4) GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 33 commands at the merged head -- byte-identical family set to the pre-merge derivation. All 33 run in the foreground, each exit code captured before any pipe: 33 green, 0 red. dispatch-gates --ran reconciliation :: exit 0 -- '33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN ... a DERIVED zero -- all 33 recorded an exit code and none of them is 3', and no STALE TREE warning. (5) LINT: pnpm lint (eslint . --no-inline-config, the whole repo, not a narrowed run) :: exit 0 at c2a54670. (6) Control-byte self-scan on the changed file: grep -naP over the C0 set plus DEL -- no match. (7) NOT OWED and not run, with reasons: no package is touched (scripts/pm is not a workspace package), so there is no dependency-closure build and no package test/typecheck; git grep over *.test.ts / *.test.mjs / *.spec.ts finds no test naming board-snapshot, so the tool-script clause is discharged by check:pm-board-snapshot, which IS the file's own --self-test and is in the 33. The live archiver was never run against the real board from this worktree.",
      "files_changed": [
        "scripts/pm/board-snapshot.mjs"
      ],
      "premise_checks": {
        "P1": "CONFIRMED. selectWalkPlan reaches `incremental` only after `history` completes, so while the backfill runs no walk carries a recent since; in the history phase the cursor used is walk.history.cursor and resume.since equals it. board-archive tip b7c5f578 board/manifest.json reads walk_phase: history, walk.open_set.complete: true (completed_at 2026-09-10T15:39:49.484Z), walk.history.cursor: 2026-08-03T11:51:21Z, next_since: null, resume.stopped_by: budget, requests: 800, board.read_at: null, count_check.verdict: pending. Also confirmed the mechanism for board.read_at: readBoardArithmetic only ran when the open phase completed in that run or when the run did NOT stop -- a history run always stops on budget, so it never ran.",
        "P2": "CONFIRMED. git ls-tree -r over board/issues/ on a shallow fetch of board-archive into a private ref (refs/os18045/board-archive, the shared checkout never switched): 5232 files, highest number 17460. 18010, 18020 and 18025 each matched zero files; 17460 matched one.",
        "P3": "CONFIRMED. 16 open pull requests (one short page, so the listing is complete); 207 changed-file rows read once across them; zero rows name scripts/pm/board-snapshot.mjs. Neighbour worth knowing: #18033 touches scripts/pm/check-half-states.mjs, which this file imports EXIT_PREREQUISITE_NOT_MET, PROXY_FLAG, labelNames, proxyRearmPlan and resolveSweepRepo from -- it landed as 226970bb and is merged into this branch, self-test still 125 green.",
        "P4": "CONFIRMED. .github/workflows/board-snapshot.yml is untouched: same cron 7 2,8,14,20, same SNAPSHOT_ARGS, same single GITHUB_TOKEN, same 800 default (the workflow never passes --max-requests, so DEFAULT_MAX_REQUESTS is the budget). No premise forced a workflow edit."
      },
      "deviations": [
        "THE DELTA ANCHOR. The card spells the delta's since as 'the previous manifest's generated_at, minus a small skew'. Implemented as an ordered chain instead: the delta's own stored cursor; else walk.open_set.completed_at minus the skew; else next_since; else generated_at minus the skew. Reason: on the measured manifest generated_at is 2026-09-13T14:22Z while the board was last enumerated in full at 2026-09-10T15:39Z, so the card's literal anchor would have declared a three-day window this tool never walked -- the very gap the card was filed about -- and skipped it with the walk added to stop skipping it. Once a delta has run, its own cursor is the anchor and the two readings coincide. DELTA_SKEW_MS is 30 minutes, two runs' worth of the workflow's 15-minute job timeout.",
        "THE HARNESS. The card said to reuse the existing offline harness ('--self-test drives the walk with injected fetch'). No such harness existed: --self-test only exercised pure functions and never called snapshot(). Built one rather than pinning the walk order by reading the file's own source text, which would go green the moment two statements are reordered.",
        "ACCEPTANCE NOTES. The two out_of_scope_findings below are carried in this report only, not in a '## Acceptance notes' section of the PR body. Reason, measured and recorded in pm-dispatch references/platform-readings.md: a raw REST PATCH of a PR body appends a bare footer while keeping the existing one (two footers), and the prescription for that cell is to send a body with NO footer -- which would downgrade this PR's session-URL footer to the platform's bare form and lose the durable session attribution the PR-body form exists for. Neither finding is a defect, so a correct body was not rewritten to carry them."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: .github/workflows/board-snapshot.yml line 95 calls the archiver's '70-case --self-test'. That count was ALREADY stale on origin/main before this change (89 cases there, 125 here), so this diff does not falsify it -- it was false already. A comment, read by no gate; not a reproducible defect, not a contract violation, not an authoring trap. Carrier: the next PR that edits that workflow file, which is the only thing that reopens it.",
        "noted, not filed: requestCount is a module-global counter that makeBudget() closes over, so two snapshot() runs in one process share one budget and the second would see it already spent. Harmless for every shipping caller (the CLI runs one snapshot per process) and the new offline harness zeroes and restores it around each drive, which is why that zeroing exists. An observation about a shape, not a defect anything reaches today. Carrier: whoever next needs two runs in one process."
      ],
      "mcp_calls": "0 -- every GitHub read on this card went through git or the repo-scoped REST proxy (issue and comments, 16 open PRs and their file lists, the PR create, the label write, this comment), and the seat's 15:52Z instruction routes every write through REST so it is authored as claude[bot]. No MCP GitHub tool was called."
    }

    Generated by Claude Code

  3. claude commented on Sep 13, 2026

    @claude
    Contributor

    ACCEPT — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T16:12Z. The os-dev-report (5654430206) is read in full; PR #18054 head c2a54670 reviewed in-seat at the contract-review tier: ## Contract review PASS on the PR (the anchor deviation and the harness the dev built are accepted). NOT GOVERNED (scripts/pm/**) ⇒ this seat flips ready and arms auto-merge; Fixes #18045 closes this card on landing. The maintainer's direct order 「现在就派发处理 备份缺口」 is delivered on landing; the R2 copy still waits on the four secrets.


    Generated by Claude Code

  4. claude commented on Sep 13, 2026

    @claude
    Contributor

    Landed — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T16:46Z. PR #18054 merged by the queue as e248c4d6 (single parent b06b2db5); two readings at 2026-09-13T16:46Z: the queue ref gh-readonly-queue/main/pr-18054-* is gone, and git log origin/main carries (#18054) with (#18033) as the lit control. NOT GOVERNED landing: review of record 5654442313 (PASS on c2a54670), ACCEPT 5654442689, --pair 18054 exit 0, ready + auto-merge at 2026-09-13T16:12Z. The maintainer's direct order 「现在就派发处理 备份缺口」 is delivered: from the 20:07Z scheduled run the archiver walks a bounded delta of the live board first (300 of 800 requests), so every card created or updated since 09-10 enters board-archive — and R2 (live since the maintainer's 15:27Z run) — within a few runs. Residue (pm:dispatched, assignee) stripped in this pass and read back.


    Generated by Claude Code

  5. added 4 commits that reference this issue on Sep 17, 2026
    e248c4d
    9d288d4
    e592b4b
    e909148
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions