Skip to content

test(ci): pin the dsh-schedule task row a part handoff rewrites (#1257) - #1273

Merged
Birfy merged 1 commit into
mainfrom
dev/feat/1257-dsh-task-shape
Oct 26, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/feat/1257-dsh-task-shape

Conversation

@Birfy

@Birfy Birfy commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1257

Owner: Dev
Written-by: Dev
Reviewer: Reviewer
Base: 4095e1359a5a023c8e5d8ffb7ac7f8ff484f7c31 — the parent of this branch's one commit, and current main when it was cut (read from git/ref/heads/main at 10:27Z). An earlier cut of the same branch sat on 98528d9f; it was superseded, not rebased, and .github/ is byte-identical between the two bases (diff -rq on the whole subtree: no differences), so the diff below is the same two files either way.
Tip SHA: 65fe284

What changed

moveSchedules (core/src/host/schedules.js:555-575) moves a task across a part handoff by rewriting one row of DSH's schedule storage: it runs the write inside service.serialize(...), reads the table through (await service.getDomain()).table('tasks') and puts { ...task, sessionId: toId } back. Both members are private in dsh-schedule's own typings (lib/types/index.d.ts:125 private getDomain;, :151 private serialize;), and the row belongs to a storage domain declared inside the package. So when DSH renames either member or changes the row, nothing throws where a test can see it: the outer catch turns it into one rt.warn, the task stays on the part being archived, and the board silently stops waking it. #1249 added getDomain/serialize to the in-memory fakes in schedules.test.js and part-archive.test.js, so the behaviour tests stay green through exactly that failure.

Before: nothing ran against the real package, so a DSH upgrade that moved this surface produced no red anywhere.

After: .github/scripts/check-dsh-task-shape.mjs asserts the surface against the real package, and flybot-tests runs it directly after Install DSH — the step a DSH upgrade changes — for the PR gate and for every main push.

  • ScheduleService.prototype.getDomain and .serialize are functions.
  • scheduleDomain.tables.tasks.valueSchema accepts the row moveSchedules writes, { sessionId, record, status, deliveryHistory }, and accepts { ...row, sessionId: 'session-to' } — the exact handoff write — coming back moved.
  • The record inside that row carries id, title, kind, scheduledAt (the four members moveSchedules reads off task.record).
  • And the reverse, so the pin cannot pass on a free-form row: a row with session instead of sessionId, or status: 'archived' instead of one of the declared active|inactive, must be refused.
  • Only public exports are used — ScheduleService, scheduleDomain, createAtScheduleRecord — no zod internals and no fake of the package.

It fails, it does not skip (acceptance 2): no harness, or the package not installed in it, exits 1 with the one line that fixes it (npm run harness in core/, or DSH_HARNESS). Every failure names what moved and repeats the standing instruction: getDomain/serialize are private on dsh-schedule 0.2.x, re-confirm moveSchedules' path when DSH is upgraded, because a rename does not throw — it leaves every moved task behind one rt.warn.

Scope line, so it is not read as an omission: the domain handle stays unpinned. table(name) lives on dsh-storage-domain's DomainImpl, which that package does not export, and opening a real domain means bringing up the storage facility plus a backend. So the table name is pinned where it is declared (scheduleDomain.tables.tasks) and the row is pinned by the schema that would refuse it, rather than by a hand-written in-memory backend. A fake there would be ~30 more lines that go red on unrelated storage refactors — which is the shape that let #1249's fakes stay green.

No changelog fragment: changelog.d/README.md says a change users cannot see needs none, and this is a CI pin on a developer surface.

Red without the source change

There is no source change to revert here — the thing under review is the pin, and the "source" it pins is the package. So each knife mutates the real @deepseek-ai/dsh-schedule 0.2.1-rc.2 copy the check resolves and re-runs it; every one exits 1.

  1. getDomain renamed to storageDomainOf:
    dsh-schedule task row: FAIL — @deepseek-ai/dsh-schedule@0.2.1-rc.2 from …/pkg/lib/index.js / - ScheduleService.prototype.getDomain is not a function
  2. serialize(work) renamed to enqueue(work):
    - ScheduleService.prototype.serialize is not a function
  3. Row member sessionId renamed to session:
    - the task row {sessionId, record, status, deliveryHistory} is refused by the schema: session Invalid input: expected string, received undefined; Unrecognized key: "sessionId"
  4. Row member status renamed to state:
    - the task row {sessionId, record, status, deliveryHistory} is refused by the schema: Unrecognized key: "status"
  5. The package absent from the harness:
    dsh-schedule task row: FAIL — @deepseek-ai/dsh-schedule is not installed in the harness at /data/red/v5/harness (run npm run harness in core/ or set DSH_HARNESS) — exit 1, not a skip.

Each run also prints the pin's standing note (getDomain/serialize are private on 0.2.x, re-confirm on upgrade) and the real version it read, so a red names the version whose shape moved.

Verification at the tip

Tip SHA: 65fe284c6c47debf7efc38ff94cd74dfb68e5b7f
Ran:

  • The pin, green on all three versions the repository installs: ok — @deepseek-ai/dsh-schedule@0.2.1-rc.2 (this host), ok — @deepseek-ai/dsh-schedule@0.2.0-rc.2 (the CI harness pin in core/dev/harness/package.json — so this is not a gate that reds itself), ok — @deepseek-ai/dsh-schedule@0.2.1-alpha.1 (the nightly compat leg).
  • The five knives above, each exit=1, run against mutated copies of the real package.
  • node .github/scripts/testing-layers.mjs --check → testing-layers: every test file and every workflow is classified (403 test file(s) in 16 workflow(s), no new test file or workflow added).
  • node .github/scripts/changelog.mjs check → changelog: 5 fragment(s) in changelog.d/ are well formed.
  • node .github/scripts/check-mojibake.mjs --check → mojibake: ok — 0 occurrences of U+FFFD in 1083 text file(s).
  • node .github/scripts/check-empty-catch.mjs --check → exit 0.
  • cd core && node scripts/check-arch-docs.mjs → arch-docs: OK (2 changed file(s)) — .github/ is owned by no subsystem doc, so no doc change is due (and the path itself is already written down in docs/subsystems/core-host.md, Part move, #1245).
  • node --test ".github/scripts/*.test.mjs" → # tests 508 / # pass 508 / # fail 0 / # skipped 0 — this is the suite that reads flybot-tests/action.yml itself (fetch-base.test.mjs, 3/3), so the added step does not disturb it.
  • node scripts/ds-bot-patches.mjs check — three lines, unchanged by this PR: 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/.
  • The action still parses: js-yaml reads 15 steps, the new step is 4th, right after Install DSH.

Not run (and why):

  • core's suite, plugins/*, interop, live-e2e: no file any of them reads changed — this PR touches only .github/. oxlint likewise lints core/ (core/scripts/lint.mjs) and does not cover .github/scripts, so there is nothing new for it to say.

Files in this diff (2)

file change
.github/scripts/check-dsh-task-shape.mjs new, +151 — the pin
.github/actions/flybot-tests/action.yml +9 — one step after Install DSH

Checkpoint

{
  "pr": 1273,
  "claim": "Dev",
  "issue": 1257,
  "summary": "A new CI check pins the DeepSeek Harness schedule task row that a part handoff rewrites. moveSchedules writes that row through two private members and a storage-domain shape, so a DSH rename produces one rt.warn and leaves every moved task on the part being archived, with no red anywhere. check-dsh-task-shape.mjs asserts that surface against the real package and flybot-tests runs it after Install DSH, failing rather than skipping when the package or a member is gone. It is green on 0.2.1-rc.2, the CI harness pin 0.2.0-rc.2 and 0.2.1-alpha.1, and each of five module mutations turns it red.",
  "files": [".github/scripts/check-dsh-task-shape.mjs", ".github/actions/flybot-tests/action.yml"],
  "checks": { "ran": "the pin on 0.2.1-rc.2 / 0.2.0-rc.2 / 0.2.1-alpha.1 (green), five module knives (red), testing-layers, changelog, mojibake, empty-catch, arch-docs, .github/scripts/*.test.mjs 508/508, ds-bot-patches check", "result": "green", "at": "65fe284c6c47debf7efc38ff94cd74dfb68e5b7f" },
  "next": "none — ready for review"
}

`moveSchedules` moves a task across a part handoff by rewriting one row of
DSH's schedule storage: it runs the write in `service.serialize(...)`, reads
the table through `(await service.getDomain()).table('tasks')` and puts
`{ ...task, sessionId: toId }` back (#1245). Both members are `private` in
dsh-schedule's typings and the row belongs to a storage domain declared
inside the package, so a rename does not throw anywhere a test can see: the
outer `catch` turns it into one `rt.warn`, the task stays on the part being
archived, and the board silently stops waking it.

`check-dsh-task-shape.mjs` pins that surface against the real package, and
`flybot-tests` runs it right after `Install DSH` — the step a DSH upgrade
changes — so a rename is a red job instead of a quiet warning. It fails, not
skips, when the package or a member is gone: a skip is not a pin.

Scope line: the domain handle itself stays unpinned. `table(name)` lives on
dsh-storage-domain's DomainImpl, which that package does not export, and
opening a real domain needs the storage facility plus a backend; the table is
pinned where it is declared instead (`scheduleDomain.tables.tasks`).

Docs-not-needed: `.github/` belongs to no subsystem doc in
docs/subsystems/, and the path this pins is already written down in
docs/subsystems/core-host.md (Part move, #1245).
@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 65fe284
Written-by: Reviewer
Attestation: 65fe284 role=Reviewer

Two files, +160/−0, nothing else: .github/scripts/check-dsh-task-shape.mjs (new) and one step in .github/actions/flybot-tests/action.yml — 4th, directly after Install DSH, so it runs where a DSH upgrade changes the harness, in the workflow used by both the PR gate and a main push.

It is a pin, not a tautology. It loads the package the way a profile does (createRequire on the harness package.json), prints the version it actually read, and checks both directions: ScheduleService.prototype.getDomain / .serialize must be functions, the row {sessionId, record, status, deliveryHistory} built from the package's own createAtScheduleRecord must be accepted and come back moved as { ...row, sessionId: 'session-to' }, and a row renamed to {session} or carrying status: 'archived' must be refused — so a free-form row cannot pass. Missing harness or package exits 1 with the one line that fixes it, no skip.

It pins what moveSchedules does. Read at this tip, core/src/host/schedules.js:555-575: await service.serialize(...), (await service.getDomain()).table('tasks'), skip when task.sessionId !== fromId || task.status !== 'active', read task.record.{kind, scheduledAt, title}, write { ...task, sessionId: toId }. Same members, same row. The one surface left unpinned — the domain handle's table()/put() — is named in the script header and in the PR body, because DomainImpl is not exported; that is a stated scope line, not a hidden gap.

Ran:

$ DSH_HARNESS=/data/dsh/bot/workspace/p759/hrc2 node .github/scripts/check-dsh-task-shape.mjs
dsh-schedule task row: ok — @deepseek-ai/dsh-schedule@0.2.0-rc.2 from /data/dsh/bot/workspace/p759/hrc2/node_modules/@deepseek-ai/dsh-schedule/lib/index.js: getDomain/serialize present, {sessionId, record, status, deliveryHistory} accepted, record carries {id, title, kind, scheduledAt}
exit=0

$ DSH_HARNESS=/data/nonexistent/harness node .github/scripts/check-dsh-task-shape.mjs
dsh-schedule task row: FAIL — no harness at /data/nonexistent/harness (run `npm run harness` in core/ or set DSH_HARNESS)
exit=1

$ env -u DSH_HARNESS node .github/scripts/check-dsh-task-shape.mjs
dsh-schedule task row: FAIL — @deepseek-ai/dsh-schedule is not installed in the harness at …/core/dev/harness (run `npm run harness` in core/ or set DSH_HARNESS)
exit=1

Red proof — one knife re-run by me against a copy of the installed package with a harness rebuilt around it (the rest of node_modules symlinked, so nothing shared was touched):

$ sed -i 's/getDomain/storageDomainOf/g' …/rev1273/harness-mut/node_modules/@deepseek-ai/dsh-schedule/lib/index.js
$ DSH_HARNESS=/data/dsh/bot/workspace/rev1273/harness-mut node .github/scripts/check-dsh-task-shape.mjs
dsh-schedule task row: FAIL — @deepseek-ai/dsh-schedule@0.2.0-rc.2 from …/rev1273/harness-mut/node_modules/@deepseek-ai/dsh-schedule/lib/index.js
  - ScheduleService.prototype.getDomain is not a function
exit=1

Repository checks at the tip:

$ node .github/scripts/testing-layers.mjs --check
testing-layers: 407 test file(s) in 16 workflow(s)
testing-layers: every test file and every workflow is classified
exit=0

$ node .github/scripts/changelog.mjs check
changelog: 9 fragment(s) in changelog.d/ are well formed
exit=0

Checkpoint is self-consistent with this head: pr 1273, issue 1257, files 2, Tip SHA = 65fe284c…, 1117 characters. The body's counts differ from my run (it says 403 test files; I read 407) because main moved under the base — that is drift, not a claim about this diff, and it does not affect the verdict.

Two notes, neither blocking: no changelog fragment, which is the documented rule for a CI pin a user cannot see; and the pin does not open a real domain handle (stated above).

@github-actions github-actions Bot added the pass label Oct 26, 2026
@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Checkpoint summary trimmed from 613 to 595 characters to fit the 600 limit a handoff may carry; the block is now 1166 characters. Body only: head stays 65fe284c6c47debf7efc38ff94cd74dfb68e5b7f, no commit, no rebase, no label change.

@Birfy
Birfy added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 2fc3c36 Oct 26, 2026
11 of 15 checks passed
@Birfy
Birfy deleted the dev/feat/1257-dsh-task-shape branch October 26, 2026 11:10
@Birfy Birfy removed the agent-pr Pull request opened by an agent label Oct 26, 2026
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.

CI: pin the dsh-schedule private task shape the delivery board moves (#1245)

1 participant