Skip to content

test(runtime): hold clock-derived header values fixed in the /meta action-entry wire comparison - #31195

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6m6ski
Nov 7, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6m6ski

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread

Fixes #31171

Clause-②: no
Metadata-protocol: none

维护者速读

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:

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

Ablation (one-time, the test file mutated through scripts/ablation-replace.mjs, restored to HEAD after each leg)

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 (784ab7096c9c) and git diff HEAD is empty after it. The test file runs from source, so no dist/ is involved.

Repeat runs
  • 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.

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.

Derived gates (node scripts/pm/dispatch-gates.mjs --commands, 50 commands)

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.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 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.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

🤖 Generated with Claude Code

…ues held fixed

The ADR-0106 D1 action-entry describe compared whole wire texts, including
the Last-Modified header the protocol stamps with the time of the read at
one-second precision. Two sequential reads that straddled a second differed
there only and ejected unrelated PRs from the merge queue. wireText now reads
a date-valued Last-Modified or Date header as a fixed token; status, body,
ETag, every other header, and the clock header's presence are still compared.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2gH1g3Qk8UMKLnGKoq
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8acbfab9b4d9c0f6df84d4f350b364fe5eb8d4fc → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F

席位核对 — ACCEPT · domain:server#4 · Head-sha: fc6ee7415f4323972688dd66469a89b57146259d

  • Shape: draft → main, attribution lines first, Fixes #31171 once, Clause-②: no and Metadata-protocol: none line-initial; test-only diff (skip-changeset). 1 file, +21/−1, packages/runtime/src/domains/mcp-object-schema-mask.test.ts; not governed, under 3000 lines; local git merge-tree against current origin/main clean.
  • Diff read by the seat: wireText replaces the value of a Last-Modified / Date header with the token CLOCK only when it parses as a date; status, body, ETag, every other header, header order and the clock header's presence stay compared. No assertion loosened beyond the clock value; every wireText caller (absent-name equivalence, CONTROL, exempt/unsecured) keeps its byte claim.
  • Cause confirmed on both hits: fix(plugin-security): canReadObject, getReadFilter and security/explain answer the delegated business-unit read as the read door does #31142 (build 42383103865, line 801, st_ping, 14:49:31 vs 14:49:30) and feat(cli): os mcp connect registers your environment's MCP server with Claude Code #31149 (build 42385456389, line 785, st_rename, 15:19:24 vs 15:19:23): body, ETag and header order equal; triage's key-order guess does not hold.
  • Gates (check-runs on the head): 53 runs — 35 success, 18 skipped, 0 failed; dev's local dispatch-gates --ran 50/50, 0 NOT-MEASURED; runtime 554 files / 8918 tests, typecheck, eslint green; three-leg ablation (1 s fake-Date skew: old helper red, new green; header dropped on one side: new red) and 15 consecutive runs green (report 6710363726).
  • Accepted deviations: none.
  • Out of scope: the product half — the cached /meta item read stamps lastModified with the read time — filed as metadata: the cached /meta item read sends Last-Modified as the time of the read, not of the item, so the header carries no information #31196 (finding). The anchor closes with this PR (Fixes).
  • Landing: Tier: none. Seat lands: pr_ready + automerge_enable through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 7, 2026 17:32
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 7, 2026 17:32
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 42396875046 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (6/8) — 失败步骤: Run this shard's tests

    @objectstack/plugin-auth:test:  FAIL  src/organization-access-policy.test.ts > a switch of the active organization is judged against the target's limits > the absolute limit is judged too, and a targe
      ↳ 失败原因: @objectstack/plugin-auth:test: AssertionError: expected 'ended' to be 'admitted' // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 9 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F

Queue ejection read · build 42396875046 · Test Core (6/8) · packages/plugin-auth/src/organization-access-policy.test.ts (expected 'ended' to be 'admitted'), anchor #31212.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Queue-flake anchor: src/domains/mcp-object-schema-mask.test.ts

2 participants