Repository navigation
fix(rest): fold the switched-off action names into the cached object read's ETag - #28094
Conversation
…read's ETag The default cached GET /meta/object/:name serves disabledActions beside the item, but kept it out of the validator and therefore stood every 304 down for a read that carried the key. On a default deployment (the activation ledger attached) no object read ever revalidated to 304. Fold the served list into the ETag the REST layer already folds and judges for objects, so an unchanged object under an unchanged switch set revalidates to 304 and a switch flipped off or on answers 200 under a new ETag. Absent folds nothing (the ETag stays byte-identical); [] folds a fingerprint of its own. Only the flow registration, which is in no validator, still stands the 304 down. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
…read's ETag The disabledActions describe string said the list was never part of the ETag, so a cached read carrying it was never answered 304. The REST door now folds it in, so that sentence would contradict the runtime. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
📓 Docs Drift CheckThis PR changes 2 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 171 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 7c1a3044673f58300ebeed81119a2aa297eacc45 && git checkout 7c1a3044673f58300ebeed81119a2aa297eacc45
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 59547058aeb3eccde6f267293367dabf5ef82e59 cd36d2103e04e824c8dbb0dc973e87a0709dc2bf && git checkout -B drift-repro 59547058aeb3eccde6f267293367dabf5ef82e59 && git merge --no-ff cd36d2103e04e824c8dbb0dc973e87a0709dc2bf
node scripts/docs-audit/affected-docs.mjs --json 59547058aeb3eccde6f267293367dabf5ef82e59
|
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head b45e452 · 2026-10-30T12:36Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Rendered 2026-10-30T12:46Z on the schema face ( ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: FAIL |
…ist and the ETag The GetMetaItemResponseSchema docblock still said a read that carries disabledActions is never answered 304 on the cached door, and the cached response schema did not say that the REST door folds the served list into the ETag it checks. Both now say what the REST door does. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head fd4f08b · 2026-10-30T12:50Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Delta review rendered 2026-10-30T12:52Z on the schema face ( ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
…istration Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head cd36d21 · 2026-10-30T12:53Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Second delta review rendered 2026-10-30T12:54Z on the schema face ( ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL 维护者速读
席位意见:ACCEPT。 席位核对Head-sha:
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Fixes #28044
维护者速读
GET /meta/object/:name把随对象下发的disabledActions(被启用台账关掉的动作名)折进 ETag;没变化时重新验证答 304,管理员开关动作后答 200 并换新 ETag。spec 里这个键的描述同步改成"折进 ETag"。disabledActions放到对象读旁边时,为了正确性让每个带这个键的读都放弃 304;默认部署都挂着台账,所以控制台每次读对象都重新下载整个定义。packages/rest/src/meta-item-read-routes.ts的besideJudged(现在只有 flow 的registration还让 304 让位)和文件末尾的cachedDisabledActionsFingerprint;然后看meta-object-cached-disabled-actions.test.ts。pnpm --filter @objectstack/rest exec vitest run src/meta-object-cached-disabled-actions.test.ts,应看到 8 passed,其中 rest: a cached GET /meta/object/:name that carries disabledActions is never answered 304 — fold the switched-off names into the ETag so object reads revalidate again #28044 的 5 条(304 / 关掉后 200 / 打开后 200 / 缺失与 [] 不同 / 无台账对照 304)。Clause-②: no
Metadata-protocol: none
What changes
packages/rest/src/meta-item-read-routes.ts, the cached branch ofGET /api/v1/meta/:type/:name:disabledActionsserved beside the item is now a fifth dimension folded into the ETag that this layer already builds and checks for objects (audienceFolds). The fingerprint is a hash of the list exactly as served.[]folds a fingerprint of its own, so a ledger being attached or detached also changes the ETag.besideJudged, which skips the 304, is now set only by the flowregistration. That verdict is in no validator, so a flow read that carries it still never gets a 304.packages/rest/src/meta-object-cached-disabled-actions.test.ts: the old pin "a read that carries the key is never answered 304" is replaced by five rest: a cached GET /meta/object/:name that carries disabledActions is never answered 304 — fold the switched-off names into the ETag so object reads revalidate again #28044 pins:[]and a new ETag;[]give different ETags, in both directions;"etag_task".packages/spec/src/api/protocol.zod.ts: only theGetMetaItemResponseSchema.disabledActions.describe()string. It said the list is "never part of the ETag, so a cached read that carries it is never answered 304"; it now says the list is folded into the ETag, so a cached read revalidates to 304 only while the switched-off set is unchanged. The generatedcontent/docs/references/api/protocol.mdxline follows. No shape changes.patchchangeset for@objectstack/restand@objectstack/spec.The response body is unchanged and no accepted input changes. The list read
GET /meta/objecthas no cached or ETag door (meta-collection-routes.tshas no ETag handling), so it has nothing to pin.Why
#27964 put
disabledActionsbeside the object read but kept it out of the validator. To avoid sending a stale list with a 304, it skipped the 304 for every read that carried the key. On a default deployment the ledger is attached, so every cached object read was a full 200. The maintainer approved #27964 as built and asked for the 304s back. Folding the served list into the validator means a 304 is correct again: the ETag now covers what is actually sent.Verification
Measured before the fix (tree c1b52de)
A cached object read with
disabledActions: ['showcase_mark_done']was sent back its own ETag as If-None-Match and got 200, not 304 (AssertionError: expected 200 to be 304). The ETag was"etag_task"whatever the switch set held. The new pins: 5 failed, 3 passed. One part of the control already passed before the fix (no ledger → 304); its[]part failed (200).After the fix and ablation (commit 8efc06e)
meta-item-read-routes.tsrestored to its c1b52de version; the same 5 pins went red, 3 passed. Restored withgit checkout HEAD -- <path>;git diff HEADempty.Package suite and gates (commit 8efc06e)
pnpm --filter @objectstack/rest test: Test Files 353 passed (353); Tests 6801 passed, 332 skipped (7133).pnpm --filter @objectstack/rest typecheck: OK..tsfiles with--no-inline-config: exit 0.dispatch-gates --commands: 60 derived; 59 ran with exit 0.check:dual-build-cjs-loadsneeds a whole-tree build, so it is left to CI.Spec string and merge of main (head b45e452)
pnpm gen:schemaandpnpm gen:docsinpackages/spec: exit 0; onlycontent/docs/references/api/protocol.mdxchanged among tracked files.origin/main(c4390f7): the pins file plus the meta-item-read tests, 11 passed;packages/specsrc/api/protocol*tests, 225 passed.check-commit-card-trailers --range origin/main..HEAD: exit 0.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL