Skip to content

Commit 5cba8fc

Browse files
test(dogfood): packaged-action-disabled-dispatch-live picks a not-done task and reads its own ledger row (#27066)
Fixes #26952 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:只改一个 dogfood 测试文件,让「禁用打包动作」测试挑一个确定未完成的任务,并让 acceptance[5] 自己读取账本行。 - 为什么改:原来不加过滤和排序地取第一条任务,种子数据里有已完成的任务,取到它时 acceptance[0] 失败并连带 acceptance[5],main 已连续变红六次。 - 风险与回滚:纯测试改动,不动产品代码,产品断言(409 `ACTION_DISABLED`、零写入)原样保留;回滚即 revert 本 PR。 - 先看:`packages/qa/dogfood/test/packaged-action-disabled-dispatch-live.dogfood.test.ts` 的 `beforeAll` 取任务一段和 acceptance[5] 开头。 - 两分钟验证:`pnpm --filter @objectstack/dogfood exec vitest run test/packaged-action-disabled-dispatch-live.dogfood.test.ts`,预期 8/8 通过。 - 需要你做的:无,席位落地。 ## What changes `packages/qa/dogfood/test/packaged-action-disabled-dispatch-live.dogfood.test.ts`, test-only: 1. **A provably not-done pick.** `beforeAll` took `GET /data/showcase_task?$top=1` with no filter and no order. The showcase seed carries `done: true` tasks (`examples/app-showcase/src/data/seed/index.ts`, the first seeded task is one), so the first row could already be done. It now reads `$filter={"done":false}&$orderby=id&$top=1`. That is the JSON `$filter` and `$orderby` spelling other dogfood files already use against this data door, for example `activity-withheld-update.dogfood.test.ts`. The setup then asserts `done === false` on the picked row before any switch flips, so a bad pick fails `beforeAll` with the sentence `setup: task ID was picked as not done`, not as acceptance[0]. 2. **acceptance[5] no longer depends on acceptance[0]'s variable.** The file-level `markDoneRowId` is gone. acceptance[5] reads the `showcase_mark_done` ledger row itself before the reboot, asserting it exists with the message `acceptance[0] disables showcase_mark_done; this clause re-reads the ledger row it wrote`, and then pins that the same row id survives the cold boot and the re-enable. acceptance[0] still asserts that exactly one ledger row exists. Unchanged: both 409 `ACTION_DISABLED` answers, the engine sentence, the `{done, progress, updated_at}` before/after equality, and `after.done === false`. No skip, no retry, no quarantine. ## Why Six main push reds since 21:56Z on `Dogfood Regression Gate (3/3)` were this file, with the same two clauses each time: acceptance[0] `expected true to be false` and the acceptance[5] cascade `expected '…' to be undefined`. ## Acceptance notes **Siblings that take `?$top=1` from a seeded object** (read on `origin/main` `cb8988ee14`). None asserts on the picked row's state, so none has this defect, and none is changed: - `api-key-owner-revoke`: `/data/showcase_task?$top=1` is an auth probe. Only the status (200, or 403 `API_KEY_REVOKED`) is read. - `bearer-lane-password-change`: two auth probes (403 `PASSWORD_RESET_REQUIRED`, then 200). Status only. - `data-request-shape-live`: the row is a handle. It PATCHes `title` and reads `updated_at` as a version token. The task's only validation is the `status` state machine, which a title PATCH never touches, so a done row behaves the same. - `identity-admin-fields-org-peer`: no `$top=1`. The seat's grep matched `$top=100`. - `internal-field-data-door-positions`: an api-key auth probe. Status 200 only. - `permission-set-assignment-window`: `door()` returns the status only. - `rest-config-contract-live`: the row id is a mount probe handle. `expectMounted` reads the routing answer, not the row. - Also matched by the substring grep, all harmless: `comment-thread-parent-pair` and `activity-withheld-row-predicate` read `.total` under a filter. `activity-withheld-update` pages with a filter and `$orderby=timestamp asc`. `master-detail-batch-atomic` and `related-list-server-paging-and-gate` use `$top=10` or more. **skip-changeset:** `@objectstack/dogfood` (`packages/qa/dogfood/package.json`) is `"private": true` and declares no `files[]`. There is no `packages/qa/package.json`. Nothing under `dogfood/test/**` ships. **CI note:** `dispatch-gates` reports that `Dogfood Regression Gate` does not run on `pull_request` or `merge_group`. Its first CI run on this change is the main push battery after the merge, so the local runs below are this PR's evidence for the file. ## Verification Head `aaaa7f96`, built with `pnpm turbo run build --filter=@objectstack/dogfood` (exit 0). Each run used the dogfood package's own vitest config (`|isolated|` project), the same config CI's shard uses, as a single file. <details><summary>Reproduction on the ORIGINAL file: done-first state forced, acceptance[0] and [5] fail exactly as on main</summary> A scratch copy, not committed, of the `origin/main` file PATCHes the unordered `?$top=1` task to `{ done: true, progress: 100 }` right after sign-in: ``` SCRATCH done-first row G2I6Iw9nF_S2Pdv5 true × acceptance[0]: the REST door answers 409 ACTION_DISABLED with the engine's sentence and writes nothing 112ms × acceptance[5] / [2]: after a cold boot the switch holds; re-enabling updates the same row and re-arms dispatch 1911ms AssertionError: expected true to be false // Object.is equality ❯ test/zz-scratch-26952.dogfood.test.ts:158:24 AssertionError: expected 'K_wXNbQ0V8WU76yh' to be undefined // Object.is equality ❯ test/zz-scratch-26952.dogfood.test.ts:249:29 Tests 2 failed | 6 passed (8) os-verify-lock: VERDICT command-exit 1 ``` </details> <details><summary>The FIXED file against the same done-first state: green</summary> The same injection applied to this PR's file: ``` SCRATCH done-first row 4wyelL2XgkXDo2eH true Test Files 1 passed (1) Tests 8 passed (8) os-verify-lock: VERDICT command-exit 0 ``` The filter is really applied: a second variant forces the **id-ordered** first task (`$orderby=id&$top=1`, unfiltered) to done. The fixed pick still skips it and stays green: ``` SCRATCH done-first row -Pm8fzBxJ3PIse1h true Tests 8 passed (8) os-verify-lock: VERDICT command-exit 0 ``` </details> <details><summary>The committed file, 3 consecutive runs</summary> ``` Test Files 1 passed (1) Tests 8 passed (8) Test Files 1 passed (1) Tests 8 passed (8) Test Files 1 passed (1) Tests 8 passed (8) ``` </details> <details><summary>Typecheck, lint, derived gates</summary> - `pnpm --filter @objectstack/dogfood typecheck`: exit 0. The tsconfig `include: ["test/**/*"]` covers the file. - `eslint --no-inline-config` on the file: exit 0. - `node scripts/pm/dispatch-gates.mjs --commands`: 51 commands derived and 51 run. 49 exited 0 and 2 exited 3 (PREREQUISITE NOT MET, so NOT MEASURED): - `check-plugin-teardown-shape --self-test` needs an unshallow clone for its pinned positive control, and grades only the checker. - `check:dual-build-cjs-loads` needs dist for packages outside the dogfood closure. - `--ran` reconciliation: `51 derived famil(ies) accounted for — 49 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)`. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYnxoqB6x8ynuZJhzXk)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent f708cf2 commit 5cba8fc

1 file changed

Lines changed: 19 additions & 7 deletions

File tree

‎packages/qa/dogfood/test/packaged-action-disabled-dispatch-live.dogfood.test.ts‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
//
3737
// The file owns its boot (and its database file): it writes ledger rows, so it
3838
// may not share a showcase with any other file.
39+
//
40+
// The task the Mark Done cases act on is picked as a not-done one in a stable
41+
// order, and its `done === false` is asserted in `beforeAll`: the seed carries
42+
// done tasks too, and an unordered first row could be one of them, which
43+
// acceptance[0]'s "nothing written" check cannot tell from a refusal.
3944

4045
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
4146
import { mkdtempSync, rmSync } from 'node:fs';
@@ -65,7 +70,6 @@ describe('[#26572] a disabled packaged action is refused at both doors, over HTT
6570
let dir: string;
6671
let dbFile: string;
6772
let taskId: string;
68-
let markDoneRowId: unknown;
6973

7074
const boot = (file: string) => bootShowcase({ databaseFile: file, extraPlugins: [new MCPServerPlugin()] });
7175

@@ -115,9 +119,13 @@ describe('[#26572] a disabled packaged action is refused at both doors, over HTT
115119
dbFile = join(dir, 'showcase.db');
116120
stack = await boot(dbFile);
117121
admin = await stack.signIn();
118-
const list = await call(admin, 'GET', '/data/showcase_task?$top=1');
119-
taskId = list.json.records[0].id;
120-
expect(taskId).toBeTruthy();
122+
const notDone = encodeURIComponent(JSON.stringify({ done: false }));
123+
const list = await call(admin, 'GET', `/data/showcase_task?$filter=${notDone}&$orderby=id&$top=1`);
124+
expect(list.status, list.text).toBe(200);
125+
taskId = list.json.records[0]?.id;
126+
expect(taskId, 'setup: the showcase seed has no task with done = false').toBeTruthy();
127+
// The precondition acceptance[0] rests on, asserted before any switch flips.
128+
expect((await readTask(taskId)).done, `setup: task ${taskId} was picked as not done`).toBe(false);
121129
}, 240_000);
122130

123131
afterAll(async () => {
@@ -152,9 +160,7 @@ describe('[#26572] a disabled packaged action is refused at both doors, over HTT
152160
.toEqual({ done: before.done, progress: before.progress, updated_at: before.updated_at });
153161
expect(after.done).toBe(false);
154162

155-
const rows = await actionRows('showcase_mark_done');
156-
expect(rows).toHaveLength(1);
157-
markDoneRowId = rows[0].id;
163+
expect(await actionRows('showcase_mark_done')).toHaveLength(1);
158164
});
159165

160166
it('acceptance[1]: the consult precedes the param contract — 409 disabled, 400 enabled, for the identical request', async () => {
@@ -233,6 +239,12 @@ describe('[#26572] a disabled packaged action is refused at both doors, over HTT
233239
});
234240

235241
it('acceptance[5] / [2]: after a cold boot the switch holds; re-enabling updates the same row and re-arms dispatch', async () => {
242+
// The row acceptance[0]'s switch wrote, read here before the reboot.
243+
const rowsBeforeBoot = await actionRows('showcase_mark_done');
244+
expect(rowsBeforeBoot, 'acceptance[0] disables showcase_mark_done; this clause re-reads the ledger row it wrote')
245+
.toHaveLength(1);
246+
const markDoneRowId = rowsBeforeBoot[0].id;
247+
236248
await stack.stop();
237249
stack = await boot(dbFile);
238250
admin = await stack.signIn();

0 commit comments

Comments
 (0)