Skip to content

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

Closed

Description

@objectstack-fleet

domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F

Filing gate: ① product defect (an HTTP cache header that states something false), found while fixing #31171 (PR #31195, dev hand-off; the flake's cause). ⛔ Not a claim. Triage sets the grade and the lane.
Reader: the seat that owns packages/metadata-protocol (protocol-search-and-cache.ts) and packages/rest (meta-item-read-routes.ts).
Dedupe (open AND closed): Last-Modified meta → 4 hits, the only related one is #31171 (the flake this causes in a test, fixed in the test only).
reach: every cached GET /api/v1/meta/:type/:name; read from the code on main, observed in queue builds 42383103865 and 42385456389 (two reads of one unchanged item answered Last-Modified one second apart).
class: c · type: Bug

维护者速读

元数据接口的缓存响应里,Last-Modified 填的是「这次读取的时间」,不是条目真正最后修改的时间。所以这个头没有任何信息:同一条没改过的元数据,两次读到的值不一样;按 If-Modified-Since 做的缓存校验永远判成「已修改」。现在缓存靠 ETag 正常工作,所以没坏功能,但这个头在说假话,今天还让一个测试在合并队列里抖了两次(#31171)。

What is wrong

  • packages/metadata-protocol/src/protocol-search-and-cache.ts ~147 returns lastModified: new Date().toISOString() beside the item's content etag.
  • packages/rest/src/meta-item-read-routes.ts ~974 renders it as Last-Modified. Two reads of an unchanged item a second apart answer different Last-Modified values with the same ETag.

Expected

  • Last-Modified is the item's own last change (its stored updated_at / publish time) when the protocol knows it, or the header is omitted when it does not — never the read time. Freshness stays gated by the ETag (no-cache), unchanged.

Acceptance: two reads of one unchanged item answer the same Last-Modified (or none); a publish moves it.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:serverDomain lane — seat card indexed by label:pm:seat
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Nov 7, 2026
  2. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p3 · domain:server · 2026-11-07T17:55Z

    ⛔ Not a claim.

    • The cached GET /api/v1/meta/:type/:name sends Last-Modified as the time of the read, not the item's own modification time. The header carries no information, and an If-Modified-Since revalidation always answers "modified". ETag revalidation works, so nothing is broken. It is also the cause of the Queue-flake anchor: src/domains/mcp-object-schema-mask.test.ts #31171 flake, which was fixed in the test only.
    • p3: a false but harmless header. Lane domain:server (metadata-protocol + rest). finding removed, pm:queue.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

    Claim: PM loop round 1 · 2026-11-07T18:10Z
    Session: session_01FEkQ5p9eu5KqN5GP7JZAGe (card thread, dev) · seat session_01FEkQ5o8PE7ZtVjcor51wBZ
    Account: os-project-manager
    Branch: claude/project-thread-qb6zil
    Worktree: objectstack-issue-31196
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/metadata-protocol/src/protocol-search-and-cache.ts, possibly packages/rest/src/meta-item-read-routes.ts, their tests, root .changeset/; ⛔ packages/spec/**
    Container & model: S, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: thread default (order asked medium; the coordinator sets effort only on the maintainer's word)
    Clause-②: no — lastModified is already optional in the published http-cache.zod.ts; omitting it or sourcing the item's own time stays inside it
    Metadata-protocol: none
    Responsibility: packages/metadata-protocol cached item read (objectstack) | ETag revalidation (no-cache) keeps caching correct | every cached GET /api/v1/meta/:type/:name (If-Modified-Since always answers modified)
    Thread-read: 6710978221
    Premise: holds: protocol-search-and-cache.ts:147 sets lastModified: new Date().toISOString() and meta-item-read-routes.ts:973-974 renders it, on origin/main 66bf5b4a
    Serial constraints cleared: none — no open PR or pm:dispatched card touches either file

    Scope: Fixes #31196. Order: /mnt/project-files/pm-orders/server/order-31196.md. Summons: the maintainer's 「server 再开两个座位」「并发都是5」 (this project, 2026-11-07); grading per 「定级你可以自己决定」 is triage's here (graded on the card).

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 7, 2026
  5. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5p9eu5KqN5GP7JZAGe

    os-dev-report
    {
    "issue": 31196,
    "status": "done",
    "branch": "claude/project-thread-qb6zil",
    "pr": "#31248",
    "session": "session_01FEkQ5p9eu5KqN5GP7JZAGe",
    "premise_still_valid": true,
    "summary": "getMetaItemCached stamped lastModified with the read time (protocol-search-and-cache.ts); the REST door only renders it. The by-name read (readMetaItem) now records the adopted sys_metadata row's updated_at (created_at fallback, canonicalIsoInstant) on its active envelope as a non-enumerable Symbol.for key (new served-row-changed-at.ts); the cached read answers it, and answers no lastModified key for an item no stored row serves, so the door sends no Last-Modified. ETag, Cache-Control and the published GetMetaItemResponse envelope are unchanged. REST door unchanged; If-Modified-Since was never read by the protocol and still never answers 304 (pinned).",
    "tests": "metadata-protocol src/protocol.cached-item-last-modified.test.ts 4/4 (same lastModified on two reads; save and publish each move it, a pending draft does not; code-shipped item has no key; envelope serialisation unchanged; ETag/no-cache CONTROL) and rest src/meta-item-cached-last-modified.test.ts 3/3, both re-run green on 7ff2b9e. Package suites on f59f3fa+565bea38 (later commits test-only): metadata-protocol 323 files passed | 4 skipped, 22531 tests passed | 26 skipped; rest 394 files passed, 7489 passed | 332 skipped; typecheck both packages exit 0 on 7ff2b9e; scoped eslint packages/metadata-protocol packages/rest exit 0 on 7ff2b9e. Ablation via ablation-replace.mjs (src-resolved, no dist): spread replaced by lastModified: new Date().toISOString(), (anchor 1->0, replacement 0->1, blob c29eebfc->c033ddc6): 3 failed | 1 passed, expected '2026-11-07T08:05:00.000Z' to be '2026-11-07T08:00:00.000Z' and expected [ Array(5) ] to not include 'lastModified'; restored blob == HEAD, git diff HEAD empty.",
    "gates": "dispatch-gates --commands: 69 families on 7ff2b9e, all run, exit 0 each; --ran: 69 derived accounted for, 69 run, 0 NOT-MEASURED (record /mnt/project-files/pm-orders/server/31196-gates-ran.txt). Earlier rounds red only on the new test doubles, fixed in-test: engine-double-contract (ledger --write), objectql-double-limit, where-matcher, test-source-alias. CI check-runs on PR #31248: in_progress at report time.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0",
    "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches → PR #31248); this os-dev-report comment via post-stamped.mjs (POST issues/31196/comments). git push not counted.",
    "open_questions": [],
    "security_findings": "none",
    "out_of_scope_findings": [
    "carrier: seat · packages/runtime/src/domains/mcp-object-schema-mask.test.ts (PR #31195) comment says the protocol stamps lastModified with the read time; false after this PR for Last-Modified (the Date header half holds), masking harmless; not touched, outside the claimed surface",
    "carrier: none · deviation: file surface grew past the claim by protocol-meta-item-reads.ts, new served-row-changed-at.ts, a one-word export in sys-metadata-repository.ts, and scripts/engine-double-contract.pinned.json (the change time is reachable only inside readMetaItem; getMetaItem envelope version is a hash digest)",
    "carrier: none · deviation: origin/main merged once (no-op at 66bf5b4); main moved to 30f23fe since, touching none of these files; not re-merged"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

    ACCEPT → PR #31248 comment 6712419529 (Head-sha 7ff2b9e528). Ready, auto-merge armed; the card closes on merge.

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

Metadata

Metadata

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions