Repository navigation
Commit fe97d85
test(runtime): hold clock-derived header values fixed in the /meta action-entry wire comparison (#31195)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiQbT4FcQAg8Z3TPX85xEdZz)_
Fixes #31171
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:只改一个测试文件。`/meta` 动作条目对比测试在比较两次响应时,把 `Last-Modified`(以及
`Date`)头的日期值视为固定占位,其余字节照旧逐字比较。
- 为什么改:这个头的值就是"读取那一刻的时间",精度到秒;两次连续请求恰好跨过一秒边界时,测试就会误报失败,今天已经把 #31142 和
#31149 踢出合并队列两次。
- 风险与回滚:仅测试代码,不发布任何包;状态码、正文、ETag 和其它所有头仍逐字比较,头的有无也仍然比较。回滚就是还原这一个文件。
- 先看:`packages/runtime/src/domains/mcp-object-schema-mask.test.ts` 里新的
`CLOCK_HEADERS` 和 `wireText`。
- 两分钟验证:`pnpm --filter @objectstack/runtime exec vitest run
src/domains/mcp-object-schema-mask.test.ts`,预期 45 个用例全部通过。
- 需要你做的:无,席位落地。
## What changes
`wireText` in the ADR-0106 D1 action-entry describe of
`packages/runtime/src/domains/mcp-object-schema-mask.test.ts` serialised
the whole `/meta` wire (status, body, headers) and every comparison in
that describe is an exact string equality over it. The REST cached arm
sends `Last-Modified`, and the protocol stamps it with the time of the
read (`packages/metadata-protocol/src/protocol-search-and-cache.ts`,
`lastModified: new Date().toISOString()`), rendered at one-second
precision. Two sequential reads that straddle a second boundary
therefore differ in that header only.
`wireText` now reads the value of a `Last-Modified` or `Date` header
(case-insensitive) as the fixed token `CLOCK` when the value parses as a
date. Everything else is compared exactly as before:
- status, body, `ETag`, `Cache-Control`, `Vary` and every other header,
byte for byte;
- the clock header's presence and its position among the headers (an
answer that gains or drops `Last-Modified` still differs);
- a clock header whose value is not a date (it is kept verbatim).
Every `wireText` caller in the describe (the absent-name equivalences,
the `layers` / `legacyLayers` / `published` reads, the member-vs-staff
CONTROL, the exempt-vs-unsecured CONTROL and the conditional-read 404)
still proves the same thing: none of them is about the time of the read.
Both queue ejections were this header, read from the Test Core logs:
- #31142, build 42383103865, job 128435166865: line 801 (exempt vs
unsecured, `usr_system item st_ping`), `Last-Modified` `14:49:31 GMT` vs
`14:49:30 GMT`, every other byte equal.
- #31149, build 42385456389: line 785 (member vs staff), `15:19:24 GMT`
vs `15:19:23 GMT` (diagnosis on the card).
No nondeterminism in the projection itself was found: in both logs the
body and the `ETag` are equal and the header keys come in the same
order.
## Tests
<details><summary>Ablation (one-time, the test file mutated through
scripts/ablation-replace.mjs, restored to HEAD after each leg)</summary>
The member-vs-staff CONTROL assertion was replaced so that the staff
read happens with the clock moved forward one second
(`vi.useFakeTimers({ toFake: ['Date'] })` + `vi.setSystemTime(Date.now()
+ 1000)`), with a precondition on the REST cached arm that the two
`Last-Modified` values really differ. Run with `-t "CONTROL — a
field-bound action"` (3 tests: REST cached, REST uncached, dispatcher).
- Leg A, new `wireText` + one-second skew: 3 passed.
- Leg B, old helper (`JSON.stringify`) + one-second skew: REST cached
arm FAILS on `st_rename`; the diff is `"Last-Modified":"Sat, 07 Nov 2026
16:14:07 GMT"` vs `16:14:06 GMT`. This reproduces the queue failure
deterministically.
- Leg C, new `wireText`, no skew, `Last-Modified` deleted from the staff
answer: REST cached arm FAILS (`"Last-Modified":"CLOCK"` against an
answer without the header), so presence is still compared.
Each leg: `ablation-replace: ok mutation landed` before the run and `ok
restored: blob == HEAD (784ab70) and git diff HEAD is empty` after
it. The test file runs from source, so no `dist/` is involved.
</details>
<details><summary>Repeat runs</summary>
- `pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2
src/domains/mcp-object-schema-mask.test.ts`, 15 runs in a loop on
dbd57b6: 15 × `Tests 45 passed (45)`, every run exit 0.
</details>
## Acceptance notes
- The `Last-Modified` the `/meta` item door sends on its cached arm is
the time of the read, not the time the item last changed
(`protocol-search-and-cache.ts` stamps `new Date()`), so it carries no
information a client can use; freshness is gated by the `ETag`. Noted,
not filed: this PR does not change it and no test here depends on it.
- The uncached REST arm and the dispatcher door send no `Last-Modified`,
so on those doors the comparison was already stable.
## Verification
Merged `origin/main` (eb68b98) once before opening; the derived gate
list was taken at fc6ee74.
<details><summary>Derived gates (node scripts/pm/dispatch-gates.mjs
--commands, 50 commands)</summary>
All on fc6ee74 (after the merge of origin/main):
- `node scripts/check-closing-keyword-parity.mjs :: exit 0`
- `node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0`
- `node scripts/check-comment-mask-adoption.mjs :: exit 0`
- `node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0`
- `node scripts/check-comment-mask-corpus.mjs :: exit 0`
- `node scripts/check-dts-emitted.mjs --self-test :: exit 0`
- `node scripts/check-issue-citations.mjs :: exit 0`
- `node scripts/check-plugin-teardown-shape.mjs :: exit 0`
- `node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0`
- `node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0`
- `node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0`
- `node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0`
- `node scripts/docs-audit/check-affected-docs.mjs :: exit 0`
- `node scripts/docs-audit/check-drift-comment.mjs :: exit 0`
- `pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit
0`
- `pnpm check:ci-filter-parity :: exit 0`
- `pnpm check:closed-ceremony-text :: exit 0`
- `pnpm check:cross-package-test-inputs :: exit 0`
- `pnpm check:doc-authoring :: exit 0`
- `pnpm check:driver-memory-census :: exit 0`
- `pnpm check:dts-closure :: exit 0`
- `pnpm check:dual-build-cjs-loads :: exit 0`
- `pnpm check:engine-double-contract :: exit 0`
- `pnpm check:gitlink-declared :: exit 0`
- `pnpm check:issue-citations :: exit 0`
- `pnpm check:keyed-text-bounds :: exit 0`
- `pnpm check:lean-entry-closure :: exit 0`
- `pnpm check:logger-receiver-detach :: exit 0`
- `pnpm check:nul-bytes :: exit 0`
- `pnpm check:objectql-double-limit :: exit 0`
- `pnpm check:org-identifier :: exit 0`
- `pnpm check:page-declaration-shape :: exit 0`
- `pnpm check:platform-object-tenancy-census :: exit 0`
- `pnpm check:published-files :: exit 0`
- `pnpm check:query-options-erasure :: exit 0`
- `pnpm check:refd-timer-probe :: exit 0`
- `pnpm check:registry-log-declared :: exit 0`
- `pnpm check:rest-log-spy-declared :: exit 0`
- `pnpm check:route-envelope :: exit 0`
- `pnpm check:slot-lookup :: exit 0`
- `pnpm check:sourcemap-no-sources-content :: exit 0`
- `pnpm check:system-context-census :: exit 0`
- `pnpm check:test-source-alias :: exit 0`
- `pnpm check:tier-file-adoption :: exit 0`
- `pnpm check:type-check-coverage :: exit 0`
- `pnpm check:type-check-debt :: exit 0`
- `pnpm check:undeclared-dep-imports :: exit 0`
- `pnpm check:vouch-for-reference-callers :: exit 0`
- `pnpm check:watch-hint-literal :: exit 0`
- `pnpm check:where-matcher :: exit 0`
Two first ran exit 3 and were rerun after building their prerequisite:
`node scripts/check-plugin-teardown-shape.mjs --self-test`
(positive-control commit 621a487 not in the shallow clone; fetched it),
`pnpm check:dual-build-cjs-loads` (no dist; full `turbo run build`,
119/119 tasks). Reconciliation: `node scripts/pm/dispatch-gates.mjs
--ran` → 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN.
Package: `pnpm --filter @objectstack/runtime typecheck` exit 0 (tsc +
check:test-typecheck); `pnpm --filter @objectstack/runtime test` exit 0,
Test Files 554 passed (554), Tests 8918 passed / 19 skipped; eslint over
packages/runtime (`--no-inline-config`) exit 0.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset. (Test-only;
`@objectstack/runtime` ships `dist` only, so nothing publishes:
`skip-changeset`.)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ihL2gH1g3Qk8UMKLnGKoq)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1c66988 commit fe97d85
1 file changed
Lines changed: 21 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
652 | 652 | | |
653 | 653 | | |
654 | 654 | | |
655 | | - | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
656 | 676 | | |
657 | 677 | | |
658 | 678 | | |
| |||
0 commit comments