Repository navigation
feat(rest,plugin-hono-server,cloud-connection): ETag + 304 on metadata lists, uncached item reads, runtime/config and localization; db/meta Server-Timing on every timed response - #28320
Conversation
…hed item doors, runtime/config and localization answer ETag + 304 under private, no-cache; meta/db Server-Timing marks on every timed response WIP: tests and changeset follow. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
…llow the uncached exit's own validator Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
…uncached item exit, localization and runtime/config; seeded db/meta timing marks; runtime/config admin timing gate Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
…f; changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
…bare bodies counted at their call sites; alias observability in cloud-connection tests The localization and runtime/config bodies are hashed before they are sent, so they are sent as call-site literals again: the route-envelope counters read the literal at the call site and the bodies are still built here. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
📓 Docs Drift CheckThis PR changes 4 package(s): 47 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 14 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 32 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 1664389afc14cfe320e970b20e8fcdcce1f00333 && git checkout 1664389afc14cfe320e970b20e8fcdcce1f00333
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b33946512c9f968432cb8b094961424220da4bc6 a3d481eb245456bdfafe9b8f77278a66290f36d5 && git checkout -B drift-repro b33946512c9f968432cb8b094961424220da4bc6 && git merge --no-ff a3d481eb245456bdfafe9b8f77278a66290f36d5
node scripts/docs-audit/affected-docs.mjs --json b33946512c9f968432cb8b094961424220da4bc6
|
…; @objectstack/types minor Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
… A: minor is earned by a metadata-protocol change alone) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm 维护者速读
席位核对 · ACCEPT ·
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm
Requested by Tesla · project thread
Part of #27627 (Target 4: metadata caching headers + Server-Timing coverage)
Clause-②: no
Metadata-protocol: none
No contract review is owed:
packages/spec/srchas no non-test change in this PR.维护者速读
/runtime/config、/auth/me/localization、元数据类型列表、每个/meta/:type列表、以及 app/dashboard/doc/book 等不走缓存的单项)现在都带ETag+Cache-Control: private, no-cache,内容没变时回304;开了耗时头时每个响应都有db、meta、total三项。meta、没跑 SQL 时缺db,看不出时间花在哪。ETag对“实际发出的内容”做哈希,权限不同的两个人拿到不同的ETag,一个人的ETag不可能换到另一个人的内容;全部是private,共享缓存不存。metadata.enableCache: false照旧关闭/meta校验头。回滚即还原本 PR。packages/types/src/representation-validator.ts(规则本身)和packages/rest/src/meta-item-read-gate.ts两条链的最后一步。pnpm --filter @objectstack/rest exec vitest run src/meta-representation-etag.test.ts;或对任一/meta/:type带上返回的ETag作If-None-Match再请求一次,得到304。What changes
On
mainthe Console's first reads sent no validator at all, so every load downloaded them in full. Each of these now answers a weakETag,Cache-Control: private, no-cache, and a bodiless304whenIf-None-Matchnames the current tag:GET /meta,GET /meta/types(type list)packages/rest/src/meta-collection-routes.tsGET /meta/:type(every list)meta-collection-routes.tsvia the shared list chaincreateMetaListAnswerGET /meta/:type/:nameon the uncached exit (app,dashboard,doc,book, drafts,?package=, member-scoped reads)meta-item-read-routes.tsvia the shared item chaincreateMetaItemAnswerGET /auth/me/localizationpackages/plugins/plugin-hono-server/src/current-user-endpoints.tsGET /runtime/configpackages/cloud-connection/src/runtime-config-plugin.tsThe validator hashes the body actually served, after every per-caller step (read gates, the app filter, the field mask, translation, the host's environment). That is the invariant
getMetaItemCachedalready states for the cached item door. It means no per-caller fold is needed: two callers whose gates differ are served different bytes and so hold different tags, and one caller's tag can never answer304for another caller's body. The rule lives once in@objectstack/types(representationEtag,ifNoneMatchHits,REVALIDATE_PRIVATE_CACHE_CONTROL), the only package the three owners share. The tag is two FNV-1a/32 lanes (16 hex chars) writtenW/"…";If-None-Matchis compared weakly per RFC 9110 §13.1.2 (lists,*, repeated headers).publicanywhere: every body here is per caller, per host, or per locale.private, no-storeand gets no tag.metadata.enableCache: false(declared "Enable HTTP cache headers (ETag, Last-Modified)") switches the/metavalidators off; the tier-2no-storeis still sent./runtime/configstays anonymous; its payload never depends on who asked.Server-Timing (gate unchanged:
OS_SERVER_TIMING=true, or an admin'sX-OS-Debug-Timing: 1):dbandmetabesidetotal. A request that ran no SQL or read no metadata says so with a zero member (db;dur=0;desc="0 queries"), so "nothing spent" no longer reads like "not measured"./metaroutes count each metadata read intometa(meta;dur=…;desc="N reads").GET /runtime/configopens the per-request gate for an admin who sendsX-OS-Debug-Timing. It resolves the caller only when that header is sent, and a resolution failure leaves the gate closed and the payload unchanged.The bundle route is untouched (sibling PR #28287).
Step 1 reading (main 69ce01d, HotCRM, seeded admin, 58 routes)
GET /runtime/configGET /auth/me/localizationGET /meta,GET /meta/typesGET /meta/:typelists, all 33 typesGET /meta/:type/:namecached items (object, view, page, action, report, …)private, no-cacheGET /meta/flow/:nameprivate, no-cacheregistrationread live beside the document)GET /meta/app/:name,/dashboard/:name,/doc/:name,/book/:nameCorrection to PR 1's claim: the
app,dashboard,docandbookitem doors carried noETagonmain. They bypass the shared item cache by design (per-caller and per-deployment gates) and took an exit that set nothing. This PR gives that exit its own body-hash validator.Server-Timing on
main:metaon 0 of 58 routes;dbonly when SQL ran;/runtime/configemitted nothing for an admin because nothing resolved the caller.Branch re-measure (same HotCRM, branch head before rebase): 58 of 58 routes carry a weak
ETagandprivate, no-cache; 57 of 58 answer304on a resend (the flow item answers 200, unchanged and by design);db,metaandtotalon all 58 for the admin header,/runtime/configincluded.Tests
packages/types/src/representation-validator.test.ts(10): tag shape and stability, weak comparison, lists,*, arrays.packages/rest/src/meta-representation-etag.test.ts(13): per list and item door, a tag is present, an unchanged body answers304, two callers with different gates (studio.accessvs none, over an unpublished app) get different tags, same-gate callers share one, a change moves it; a member presenting a builder's tag is still refused404;enableCache: false; the tier-2 posture; both chains hash what they answer;metamarks.packages/plugins/plugin-hono-server/src/server-timing-seeded-marks.test.ts: zero members on an idle request, measured members never doubled, present for an admin, absent for an unproven caller and without the gate.packages/plugins/plugin-hono-server/src/current-user-endpoints-localization.test.ts: a tag withVary: Accept-Language,304, two users never cross-revalidate, a language change moves the tag, the signed-out answer carries none.packages/cloud-connection/src/runtime-config-etag.test.ts(7): tag,304, two hosts never cross-revalidate, no caller resolved without the timing header, gate opens for an admin only, a failed resolution leaves it closed.meta-audience-plural.test.ts), the dashboard uncached pin (rest.test.ts), and the parity reference's non-holder posture (meta-list-projection-parity.test.ts).Ablation: mutating the list chain to hash the pre-gate list (
metaRepresentationCaching(raw, cacheControl)) turns exactly "two callers whose gate differs get different tags" red (1 failed / 12 passed); restored, blob identical to HEAD.Verification
Final head
a3d481eb(seat note): on top of6d31fbb1it adds only the two seat-approved doc edits (docs/OBSERVABILITY.mdServer-Timing members,content/docs/api/metadata-api.mdxlist validators) and a changeset round trip (typesminor→ back topatch, per ruling A:Metadata-protocol: noneputs every package at patch). All 124 derived gates green on it; every check-run success or skipped. The readings below are on head6d31fbb1(main53749673merged in; 23 files, +991 / -34).dispatch-gates --commands→ 94 commands, every one run with</dev/null, exit codes recorded first;dispatch-gates --ran→ 94 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. The first sweep caught two, both fixed in6d31fbb1:check:route-envelope(the new send helper declared{}, and the localization / runtime-config bodies sent as call-site literals again so their bare-body counts stay where the ruled exemption pins them) andcheck:test-source-alias(cloud-connection aliases@objectstack/observabilityto source).check:dual-build-cjs-loads,check:lean-entry-closureandcheck:dts-closuregreen on a full dts build (turbo run build --filter='./packages/**' --filter='@objectstack/dogfood^...') with the new@objectstack/typesmodule in it.types,rest,plugin-hono-server,cloud-connection,runtimeand their closure, 35/35 tasks;tsc --noEmiton cloud-connection and plugin-hono-server after the fix.rest360 files / 7127 passed (351 skipped),cloud-connection62 / 765,plugin-hono-server36 / 457,types35 / 846.runtimemeta/timing suites (incl. the parity test) 31 files / 1451 before the gate fix, which touched no runtime or rest logic.check-governed-merges --branch HEAD: 0 of 23 paths governed, 1025 changed lines.Acceptance notes
/metatwin (packages/runtime/src/domains/meta.ts): it shares both chains, so it now answersCache-Control: private, no-cachewith no runtime code change. It sends noETagand no304; the parity test pins theCache-Controlagreement between the two transports./layerspaths) are untouched.metadata.enableCache: falseis honored for the new validators.app/dashboard/doc/bookdoors is corrected above.@objectstack/typesgains a new export, so the changeset bumps it alongsiderest,plugin-hono-serverandcloud-connection.🤖 Generated with Claude Code
https://claude.ai/code/session_01L9qibz5qPfjVU7Mns3iE1H
Generated by Claude Code