Repository navigation
authz caching leg B: grants cache — coarse invalidation, default TTL=0 (off), expiry-boundary expiry, bypass list #11971
Description
Activity
⭐ Unlock: both blockers discharged and verified by content —
pm:blocked→pm:queuePM seat
domain:services, sessionsession_0194kbQJxUvv2yvsGRtuXpP5. ⛔ Not claiming and ⛔ not dispatching — see the tier note at the end.This card was found by a substance sweep of this lane's
pm:blockedinventory, not by an unlock trigger. Both of its blockers closed — one four days ago, one yesterday — and nothing re-read the card.Blockers — closed, and their deliverables measured on
origin/main@b9dd923b9⛔ "Upstream is closed" is not the test. Each deliverable was read in the tree:
Blocker Closed Deliverable verified by content #10348 2026-08-24, PR #11686 merged The id-shaped judge is consolidated and consumed: isPlatformAdminUserIdatpackages/plugins/plugin-auth/src/auth-manager.ts:1683,:2912,:3285#11968 2026-08-27, PR #12652 merged All three substrate pieces present — see below #11968's three deliverables, individually:
- Engine-seam write epoch —
packages/objectql/src/write-epoch.ts:13states it is "a generalisation of the privatewriteEpochfield";packages/objectql/src/engine.ts:3268readonly writeEpoch = new WriteEpoch(), bumped at:3354; already consumed bypackages/core/src/security/resolve-authz-context.ts:891. - The
authz.invalidatedchannel —packages/core/src/security/authz-invalidation-channel.ts:73export const AUTHZ_INVALIDATED_CHANNEL = 'authz.invalidated', withAuthzInvalidatedPayloadat:106, the bridge atpackages/objectql/src/authz-invalidation-bridge.ts, exported frompackages/core/src/security/index.ts:181. - Boot-time posture statement —
packages/core/src/security/authz-cache-posture.tsexists, with the loud line at:103/:122: "authz.invalidatedinvalidation bus — … A grant revoked on …".
⚠️ And the ruling's hard requirement survived into the code, which matters because this leg is the consumer that could quietly contradict it:authz-invalidation-channel.ts:10— "⭐ THE TTL IS THE CORRECTNESS CONTRACT. THIS CHANNEL IS NOT." ⇒ this leg's TTL bound stays the correctness mechanism; the bus only narrows the window.The "ship third" ordering is also satisfied
This card's body says leg B "ships third", which is an ordering constraint over and above its
Blocked-by:set — so it was checked rather than assumed. The full #11633 tranche:Card Leg State #11968 substrate ✅ closed #11966 leg C — localization read cache ✅ closed #11967 leg D — getMetaItemsoverlay cache✅ closed #11969 ADR conversion (governed surface) ✅ closed #11971 leg B — grants cache the only one left ⇒ Nothing precedes it any more. This is the last leg of the tranche, and "the leg the design is really about" by its own description.
⛔ Above this seat's tier — flipped, not dispatched
This card declares Clause-②: yes (authorization answers) with
needs:contract-reviewat review and the contract-review tier mandatory. This seat measuresclaude-opus-5; the contract-review tier isclaude-fable-5. It can dispatch at tier but cannot clear the label, flip ready, or arm the merge — so landing it here would strand the PR.⇒ Left
pm:queue, unassigned, for a seat that can carry it end to end. Nothing else about the ruled shape is touched by this comment: forks 1A / 4 (default TTL0, cache off), the expiry-boundary rulemin(ttl, nextBoundary), the bypass list (explain-engine.ts:473,service-automation/src/plugin.ts:812), and the measured keying traps (sys_sessionstays out; asys_memberwrite retires the organization's entries) all stand, ⛔ not re-litigable here. The required ablation of write-invalidation — proving the TTL bound alone cannot satisfy the read-after-write pins — remains part of the test plan.
Generated by Claude Code
- Engine-seam write epoch —
zhuangjianguo commented
on Aug 30, 2026 CollaboratorMore actions⚖️ 契约复审 PASS — PR #13415(项目总监席,达档机读
last_served_model = claude-fable-5)维护者召唤「处理 CONTRACT_REVIEW」下执行。审的是契约增量:授权答案面的跨请求缓存(#11633 leg B,条款② yes)。
对裁定逐条核(2026-08-25 接受的设计,非重开):
- Fork 4 默认关是真路径:
openUserGrantsCache在 TTL≤0 时于任何 wiring 之前返回undefined——零 middleware、零 epoch 订阅、零存储,实测于模块源码非采信正文。 - Fork 1A 两缝如裁:缝 1 中间件按
GRANTS_CACHE_WATCHED_OBJECTS过滤、finally内 bump(含 throw,过失效系安全方向);缝 2 只吃非'write'reason(metadata/remote/manual),本地写不重复消费——sys_session陷阱双向守住(不入 watched set + raw epoch 不用于本地写)。 - In-flight 写安全:entry 存
genAtOpen,命中要求gen === state.gen⇒ 跨写解析 dead on arrival。 - Expiry-boundary 规则:
min(ttl, nextBoundary);独立核验了 load-bearing 声明「boundary 扫描恰覆盖isGrantActive检过的行」——PR head 上isGrantActive全部调用点恰为 members(:462/:485)、userPositionRows(:504)、upsRowsAll(:525)三族,与扫描入参[...members, ...userPositionRows, ...upsRowsAll]一一对应,无漏族;含 inactive 行(futurevalid_from翻转)正确。nextGrantValidityBoundary与isGrantActive共用解析器,unparseable 永久 fail-closed 故不设界,自洽。 - Bypass 名单两站点如裁(explain-engine
buildContextForUser、automationrunAs:'user'),bypass 不读不写缓存,双半 pin 在案。 - 偏离申报收下:seeds 入键(弃 §4 B.2 的 seedless+重放)系实测否定「pure prepend」前提后取设计点名的另一选项——种子影响 ADR-0095 posture 档与
needsUserRow查询抑制,外部重放即 [finding] Three spellings of the ADR-0068 platform-admin read now live in plugin-auth, and one of them skips the system read context #10348 漂移形状;键为 JSON 数组防分隔符别名。 - 键安全:WeakMap per-engine 隔离、wiring 半途失败投毒不缓存、命中发
structuredClone防调用方污染。 - 消融方向对:去掉双 bump 后恰 5 红(写失效族)/23 绿(TTL/boundary/identity/off),失效链 load-bearing 得证。
- changeset 评级(core minor / 两插件 patch)与 env-vars 文档行更新准确,「explainer 与 runAs:'user' 恒不缓存」已写入部署文档。
处置(清标即落地):PR 侧清
needs:contract-review(卡侧未挂,dev 依 2026-08-28 裁定留予本席,无需清);PR 转 ready + 挂 auto-merge(非受管 code PR)。
Generated by Claude Code
- Fork 4 默认关是真路径:
Leg B (ship third — the leg the design is really about) of the accepted #11633 cross-request caching design. Provenance: design document = #11633 comment 5394942824 (§4 leg B, §10.4); maintainer acceptance = #11633 comment 5404681591 (2026-08-25, verbatim 「接受你的建议,继续」, forks 1A / 2B / 3A / TTL-0). Filed by PM session
session_01KWRU3s15AJz7PGW7a7wdCh.Blocked-by: #10348
Blocked-by: #11968
(#10348 per the design's dependency disclosure; #11968 is the invalidation substrate this leg consumes.)
Ruled shape (⛔ not re-litigable here):
0(cache off by default), supported low-seconds range; enabling it is the deployment's explicit acceptance of a staleness window.min(ttl, nextBoundary)— ADR-0091 validity windows see no write at their boundary, so write-invalidation is structurally blind there and the timer is the only mechanism for that class.explain-engine.ts:473andservice-automation/src/plugin.ts:812read uncached — an explainer answering from cache explains a state that no longer exists at exactly the moment an admin verifies a revocation.sys_sessionstays out of the watched set (last_activity_atwrites every minute would destroy the cache); asys_memberwrite retires the organization's entries, not the writing user's (org_user_idsdepends on other users' rows).Test plan: carries pins 1–7 and 9 from the design §7, plus the required ablation of write-invalidation (prove the TTL bound alone is insufficient for the read-after-write pins — i.e. the invalidation is load-bearing).
Discipline: Clause-②: yes (authorization answers);
needs:contract-reviewat review; contract-review tier mandatory.Acceptance criterion: with the cache ON (TTL>0), grant/revoke/role/session mutations are observed by the next request on the writing node (write-invalidation, not TTL); with default config the cache is OFF and behaviour is bit-identical to today; "cached answer ≡ uncached answer" identity pins green.