Repository navigation
fix(runtime): forward a record-less aggregate api action that declares recordIdParam - #29231
Conversation
…s recordIdParam
A record-less dispatch (no recordId) reaches buildApiActionRequest with the
subject load's empty {} record. The recordIdParam branch read that {} as a row,
found no id and refused 400, so every execution: 'aggregate' api action that
declares recordIdParam failed for the whole bulk selection and its target never
ran. The branch now seeds recordIdParam only when the dispatch names a record,
the same meaning of record-less the path half already uses; the #8018 refusals
for a named record whose row lacks the field or holds null are unchanged.
Claude-Session: https://claude.ai/code/session_01ADHkmNtfGcifoTscZGVzuo
Co-authored-by: Claude <noreply@anthropic.com>
…IdParam forward Claude-Session: https://claude.ai/code/session_01ADHkmNtfGcifoTscZGVzuo Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1 维护者速读
席位意见:与 diff 相符。修在构建器而非调度,理由成立:空 席位核对ACCEPT ·
|
…-q7jqr4 # Conflicts: # packages/runtime/src/domains/api-action-forward.test.ts
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 882ff8280c3fe40775618e4cdb5b39df7820aded && git checkout 882ff8280c3fe40775618e4cdb5b39df7820aded
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 45e9b2f14c91f76683296cd33bb1b477cbaef1bb 852d77a5b2c33bad5181f3373925efd18594e3a7 && git checkout -B drift-repro 45e9b2f14c91f76683296cd33bb1b477cbaef1bb && git merge --no-ff 852d77a5b2c33bad5181f3373925efd18594e3a7
node scripts/docs-audit/affected-docs.mjs --json 45e9b2f14c91f76683296cd33bb1b477cbaef1bb |
|
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1 维护者速读
席位意见:合并提交未改 PR 自身 diff(3 个文件 +115/-2,与 席位核对ACCEPT ·
|
Requested by Steve · project thread
Fixes #29173
Clause-②: no
Metadata-protocol: none
维护者速读
recordIdParam去取记录 id;没点名记录(批量"聚合"执行)时不再把空的{}当成一行。execution: 'aggregate'的批量动作,自 Console: send a same-origintype: 'api'action click throughPOST /actions/:object/:actioninstead of fetching itstargetdirectly (ruling on #27506, step 3 of 3) #27611 起每次都被 400 拒掉,目标接口从未被调用,弹窗显示全部失败;18.3.0 发版清单的 P1 项因此变红。recordIdParam && routedRecordId),单记录运行、记录缺字段/字段为空的两条recordIdFieldis not harvested into the grid's$select—sys_session"Revoke Session" reports success and revokes nothing #8018 拒绝都不变;回滚即 revert 本 PR。packages/runtime/src/action-execution.ts的buildApiActionRequest中recordIdParam那段;packages/runtime/src/domains/api-action-forward.test.ts末尾新增的[#29173]一组。pnpm --filter @objectstack/runtime exec vitest run src/domains/api-action-forward.test.ts,应见 77 个测试全过,其中[#29173]四条。What changes
buildApiActionRequest(packages/runtime/src/action-execution.ts): therecordIdParamblock now runs only when the dispatch names a record (routedRecordId, the same value the path half above it already uses to decide "this dispatch names no record"). A record-less dispatch arrives with the subject load's empty{}as itsrecord; that{}was read as a row, had noid, and the request was refused 400VALIDATION_ERROR("identifies its record by "id", but that field is not present on the record"), so everyexecution: 'aggregate'api action declaringrecordIdParamfailed for the whole selection and its target was never called.{}is the handler-facingctx.recordcontract for record-less and new-record actions (its own docblock says so), and the builder already defines record-less byroutedRecordIdfor the path tokens. Fixing it there gives the request builder one meaning of record-less for both halves, and both doors (REST/actionsand MCPrun_action) share it. It also stays inside the claim's file surface.recordIdFieldis not harvested into the grid's$select—sys_session"Revoke Session" reports success and revokes nothing #8018 refusals are unchanged for a dispatch that names a record: a row lacking therecordIdField, and a row holding null for it.patchfor@objectstack/runtime.Tests
One new
describeinpackages/runtime/src/domains/api-action-forward.test.ts, with a fixture in the showcase's "Recalculate Selection" shape (type: 'api',execution: 'aggregate',recordIdParam: 'recordId'):{ params: { _selectedIds } }with norecordIdanswers 200, the target is called once at/api/v1/apps/showcase/recalc, the forwarded body is exactly{ _selectedIds }, and it has norecordIdkey.run_action: the same record-less run is forwarded with the same body and no inventedrecordId.recordIdfrom that row.{},nullandundefinedrecords with norecordIdbuild the plain request; an emptyrecordIdis no id; a named record still gets bothrecordIdFieldis not harvested into the grid's$select—sys_session"Revoke Session" reports success and revokes nothing #8018 refusals (status 400, no path built).Forced test edit: the shared
boot()harness gains an optionalextraActionslist so the new fixture does not join the shared action roster. No existing assertion changed.Ablations (fix committed first; restored by ablation-replace, blob == HEAD, git diff HEAD empty)
if (recordIdParam && routedRecordId) {→if (recordIdParam) {): 3 failed / 74 passed. The REST case fails with{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Action \"Recalculate Selection\" identifies its record by \"id\", but that field is not present on the record — running without it would send a request that names no record.","httpStatus":400}}: expected 400 to be 200. The MCP case and the builder case also fail. The per-record control stays green, as expected.… && false): 7 failed / 70 passed, including the existingrecordIdFieldis not harvested into the grid's$select—sys_session"Revoke Session" reports success and revokes nothing #8018 pin and the new named-record refusals (expected undefined to be 400), so those refusal pins are live.Acceptance notes
resolveRecordIdParamSeedalready skips seeding when it has no row record, which is the behaviour this restores on the server side.Verification
Local readings at head cab3213 (origin/main 0d15f89 merged in)
pnpm --filter @objectstack/runtime typecheck(tsc plus the test typecheck): passed. Full runtime suite under os-verify-lock:VERDICT command-exit 0, Test Files 517 passed (517), Tests 9382 passed and 19 skipped (9401).pnpm --filter @objectstack/runtime exec vitest run src/domains/api-action-forward.test.ts: 77/77 passed.eslint --no-inline-config packages/runtime: exit 0, no findings.node scripts/pm/dispatch-gates.mjs --commands: 61 derived families, all run, all exit 0. Two first answered exit 3 (prerequisite not met) and passed once it was met: the teardown-shape self-test after fetching its pinned fixture commit, andcheck:dual-build-cjs-loadsafter a full packages build.--ranreconciliation: 61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN.Generated by Claude Code