Repository navigation
The durability log-level gate cannot see the catch { return null; } seeder family — 15 files outside #12923's five, and neither widening path is cheap #12981
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2and removed
on Aug 28, 2026 huangyiirene commented
on Aug 28, 2026 CollaboratorMore actions<!-- os-decision-facets -->
定级:
Bug·priority:p1·domain:services·needs-user-decision分诊席位,session
session_01Aujz2zykf5LXt3T98gRsGe。domain:services—— 读代码定落点:本卡点名的两个成员实际路径是packages/plugins/plugin-security/src/permission-set-drift.ts packages/plugins/plugin-security/src/permission-set-overlay-discard.ts按车道表
plugin-security归domain:services(2026-08-19 identity 并入 services)⇒ 卡的自判正确。⚠️ 但门那条腿落在scripts/,属domain:devx⇒ 跨车道,见裁后执行段。priority:p1(本卡自己没有定级,这是分诊加的) —— 依据是卡里那句实测:「a boot logged "RBAC catalog seeded" atinfoover zero landed rows, on a deployed plane, for weeks」,以及 #12970 的第二个成员:一条声称操作员执行过、而实际从未落地的审计记录(前后计数相等)。⇒ 这不是卫生问题,是已部署平面上的静默数据丢失 + 失真审计。⛔ 不是 p2。
「无处停放」这一条我复核成立 ✅(它是本卡整个论证的支点)
scripts/durability-degradation.baseline.json entries: [] ← 真空 正对照: $comment 解析出 28 条字符串 ⇒ 文件确实被解析了,这个零不是解析失败 ✅而且台账头自己写死了这条路:「SHRINK-ONLY … There is deliberately no
--fix/--updateflag — a generator would let a new violation be admitted by 'just run the update command', which is precisely how a gate stops meaning anything.」 以及 「an entry here means a REAL degradation that is not yet fixed」。⇒ ⭐ 把 15 个文件登记进去,等于把一份"其价值就在于是空的"的台账一次性填成 15 行债务。卡说这是「a 15-file repair programme wearing the costume of a one-line vocabulary edit」—— 这个判断我确认成立。
⭐ 卡里最该被独立记住的一句
「
⚠️ A green from this gate over such a file means NOT MEASURED for that site, not "level approved."」⇒ 这句话的适用范围远超本卡。⭐ 它是声明式词表型门禁的通用读法:词表内是判定,词表外是沉默,而门的绿色输出对两者不作区分。卡自述 #12923 第一轮就把这个绿读成了背书。⛔ 建议裁决时把这句话写进门的输出本身(见裁后执行段),⛔ 不要只留在卡里。
四棱
① 项目长远合理性
门以被调用者名字为词表,这决定了它天生只能看见"取了正确名字"的降级。
catch { return null; }家族之所以隐形,不是漏配,是形状不在语法可及范围。⇒ 长远问题是:门能否从"名字词表"升级为"控制流形状"判定。⭐ 仓里已有一次成功先例可抄 —— 台账头记载 #5241 就是这么干的:它没有把两条正确代码park进台账,而是给门加了FAILURE_PROPAGATION_CALLEES+FAILURE_PROPAGATION_SITES,从结构上证明每条出 catch 的路径都递交了失败,于是两条记录自然失效被删。⇒ 本卡要的是同一手法的第二次应用。② 实际业务拉动
高,且是最坏的一类:客户不会来报障。 #4420 是这一类的历史事故,卡引了原文:「the durable suspended-run store was attached to a table that was never created, every write failed into a
warnnobody read, and each restart silently dropped every in-flight approval. The system reported itself healthy the whole time.」 ⇒ 这一类的业务代价不是故障时长,是信任发现得太晚。#12970 那条失真审计尤其严重:审计记录的全部价值就是"它说发生过的事真的发生过"。③ 防 AI 犯错
⭐ 本卡自身就是证据链的一环:#12923 的第一轮把门的绿色读成了"级别已批准"。
⚠️ 而任何 agent 都会这样读 —— 绿色在其他一切场合都表示"查过且通过"。⇒ 这不是那个 dev 的疏忽,是门的输出语义有歧义。⛔ 修 15 个文件而不修这个歧义,下一个 agent 会在第 16 个文件上重犯。④ 创业阶段不扩散
⚠️ 这一棱要求严格排序,不要一次性开工。15 个文件同时变红、同时修,是本卡明确警示的失败形态。⭐ 可行的窄路是:先修已知有真实危害的两个(#12970),再决定门怎么办 —— 因为卡自己说这两个「now have a channel to fix, since #12923's shared reporter already takes an optional refusal log」。⇒ 通道已经存在,成本已知。
选项 × 真实可感成本
做法 客户可感 代价 A ⭐ 抄 #5241:给门加控制流形状判定(出 catch 的路径是否递交失败),⛔ 不动名字词表 逐步消除静默丢失 门改造一次;⭐ 台账保持为空 B 加 tryInsert/tryUpdate到词表 + 把 15 个登进台账,逐步清同上但更慢 ⚠️ 台账从 0 → 15,其"空即稳态"的语义当场作废C 加 insert/update到词表— ⛔ 被门自己的设计拒绝:其头部把 find/findOne/count列为「names too generic to declare repo-wide」,而.insert(有 144 个非测试调用点 / 72 文件(卡实测)。insert正在被排除的那一类里D 只修 #12970 的两个,门不动 消除两处真实危害 ⚠️ 其余 13 个继续静默,且门继续对它们输出绿色建议:D 立即 + A 作为正解
⛔ 不要捆绑。 #12970 的两个有已知通道、已知危害、已知成本,⭐ 且其中一个是失真审计记录 —— 那个不该等任何门禁决策。而门的正解是 A(#5241 已经证明这条路在本仓走得通)。
⛔ B 不建议:它用一次性 15 行债务,买一个 A 能在不产生债务的前提下达成的结果。
⚠️ 且台账头明写「the intended steady state, not a dormant file」—— 填它是在推翻一条已成文的设计意图,那本身就该是独立裁决。退路
若 A 的门改造被判定过重(#5241 的形状分析可能不便宜)⇒ 退到 D + 把那句「绿 = 未测量,不等于已批准」写进门的输出。⭐ 这条退路成本极低而收益真实:它不修任何文件,但让下一个读到绿色的 agent 不会再误读。
⚠️ 置信缺口(三条,第 1 条最重要)- ⭐ 我没有复现「15 个文件」这个数。
catch { return null; }是跨行形状,git grep逐行匹配不可靠,我不愿用一个我不能加同形正对照的查询去报一个数。⇒ ⛔ 本卡的 population 大小未经本席位独立验证,只有 plugin-security: two more swallowedtryUpdaterefusals outside the catalog seed — drift diagnostics vanish silently, and the overlay-discard audit line reports an action whose write was refused #12970 的两个成员我确认了路径与所属包。取卡者第一步应当用能跨行匹配的工具重数一遍,并给出正对照。 insert的 144 / 156 调用点我没有复测(卡自己也记录了两次测量口径不同、量级一致)。选项 C 的排除依据是门的设计声明,不是这个数 ⇒ ⛔ 数不准不影响 C 被排除。- 词表 18 条我没数出来(我的正则不匹配它的 Map 写法,返回 0 ——
⚠️ 那是我的查询坏了,⛔ 不是词表为空)。这个数不影响任何选项,记在此处只为不让它被当成一次真实读数。
裁后执行段
- 跨车道拆卡:门的改造(选项 A / 退路那句输出改动)落
scripts/⇒ 另立一张domain:devx卡。⛔ 不要一个 PR 同时改门与 15 个业务文件。 - 取 D(建议立即做):只修
permission-set-drift.ts与permission-set-overlay-discard.ts,走 fix(security): RBAC catalog seeder swallows unique-violation write failures — 'seeded 0' reported as success while a legacy index vetoes every row #12923 已有的 shared reporter 可选拒绝日志通道。⚠️ permission-set-drift.ts那处要连同「updated > 0才出报告」的门槛一起改 —— 否则全部写入被拒时仍然打印空白并读作"无漂移"。 - ⛔ 不得为了让门变绿而把
catch { return null; }改成catch {}或吞得更深。 - ⛔ 不得把 15 个登进
durability-degradation.baseline.json,除非维护者明确选了 B —— 那份台账的空状态是一条成文的设计意图,不是巧合。 - ⛔ 不得给门加
insert/update(选项 C),门自己已经把这一类排除。
相关
#12923(五个 seeder,本卡的测量出处)· #12970(15 个中的两个)· #4632(门)· #5241(⭐ 把门升级为结构判定的先例)· #4420(这一类的历史事故)· AGENTS.md → Degradation log levels
Generated by Claude Code
- ⭐ 我没有复现「15 个文件」这个数。
Claim: PM loop round 7 — batch 6
Session:session_016ZC5rNQj3WEet5HAmmAkMs
Branch:claude/issue-12981-batch6-swallow-family
Worktree:objectstack-issue-12981-b6
Domain:domain:services
File surface: to be enumerated by the dev from what remains unrepaired and unfenced (see below) — declared in its own claim comment before it edits, one changeset (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: claude-opus-5— judgement tier. The slice selection and each site's level call carry real judgement, but the repair shape is established by five landed batches.
Clause-②: no, expected — repairs restore declared=enforced on internal sinks.⚠️ Re-declareyesand STOP if a slice requires changing a published sink shape (see the #13398 constraint below); that is a different card and a different tier.
Serial constraints cleared: ✅plugin-authis free — batch 5's branchclaude/issue-12981-batch5-plugin-authis gone from origin (landed), verified byls-remote. ⛔ Fenced by live lane work, all verified as live branches this round:⛔ packages/services/service-storage/** #13547 (live) ⛔ packages/plugins/plugin-security/src/rls-compiler.ts #13552 (live) ⛔ packages/plugins/plugin-security/src/security-plugin.ts PR #13514 / #11974 (open draft) ⛔ packages/services/service-messaging/** PR #13565 / #13546 (open draft) ⛔ packages/plugins/plugin-webhooks/** PR #13565 (same) ⛔ packages/services/service-automation/src/builtin/http-nodes.ts PR #13565 (same)⚠️ Also ⛔packages/plugins/plugin-auth/src/files touched by #11973 (L3) — L3 is queued, not dispatched, so its files are not formally held, but it re-pointsensure-default-organizationand re-priceslast-admin-guard. If your slice wants those, say so in your claim and pick a different slice; I will sequence rather than let two cards contend.Scope — this is batch 6 of a programme, ⛔ not the whole card
The card is a 15-file repair programme (plus #12923's original five). Five batches have landed. ⛔ Do not attempt all remaining files. Repair one coherent slice — one package or one closely-related family — and stop. A batch that lands is worth more than one that sprawls.
Your first actions, before any edit:
- Read the card body and all 24 comments — they are the batch ledger and record what batches 1–5 already covered, plus the repair shape (fix(security): RBAC catalog seeder swallows unique-violation write failures — 'seeded 0' reported as success while a legacy index vetoes every row #12923's shared reporter takes an optional refusal log; the real helper names are
createSeedWriteRefusals/reportSeedWriteRefusals, andtryUpdatealready accepts an optionalrefusalsparameter). - Enumerate what actually remains on the tree, ⛔ not from the card's prose. The card's "15 files" was measured on
origin/main@196a6c73eand five batches have landed since — that count is stale by construction. Measure the current population of thecatch { return null; }/ swallowed-write shape yourself, and report the number you find alongside the number you repaired. - Declare your chosen slice and its file surface in your own claim comment (with your own session ID and this branch) before editing. ⛔ Do not touch
assigneesor labels.
⛔ Two hard constraints, both from rulings
1. ⛔ Do NOT widen the gate's vocabulary. Adding
insert/updateor the seeder-local helper names toDURABILITY_CRITICAL_CALLEESis the scope-and-governance call this card exists to defer — the card says so in terms, and #12923 correctly declined it unasked. Repairing sites is in scope; changing what the gate can see is not. ⛔ Nor may you add entries toscripts/durability-degradation.baseline.json: it is shrink-only, has deliberately no--fix, and its header says "CURRENTLY EMPTY — the intended steady state".2.
⚠️ The LEVEL question is ruled and is NOT always yours. #13398 was ruled by the maintainer on 2026-08-30: where a site reports through a published sink shape, raising it toerrorwould enrol every module on that type into the shrink-onlycheck:optional-error-sink-contractpopulation — option B was refused as actively harmful. So for each site in your slice:- The SILENCE is always yours to fix — count refusals, report them, and never let "every write refused" print byte-identical output to "nothing to do". That needs no contract change.
- The LEVEL is yours only if the sink is package-private. Landed precedent from batch 2:
bootstrap-system-capabilities.tsused a package-privateSeedLoggerand shipped aterror;cleanup-package-permissions.tsandsuggested-audience-bindings.tsrode sinks exported fromindex.tsand correctly stayed atwarn. - ⇒ Check each sink's export status before choosing a level, and say in the PR body which sites were level-repaired and which were silence-only-because-published. ⛔ A published sink you want to raise is [Decision] plugin-sharing's refused-backfill report lands at
warnwhere AGENTS.md puts it aterror— and the card that was supposed to carry the level is CLOSED #13398's question, not yours: report it, do not change it.
PM mechanism assumptions — measure these; I would rather be falsified
- That a coherent unfenced slice still exists. Five batches landed and six paths are fenced above. If everything remaining is fenced or already repaired, that is a legitimate delivery: report
premise_still_valid: falsewith the enumeration, and the card can be closed or re-sliced. ⛔ Do not manufacture a slice to have something to push. - That the swallow sites are still swallowing. Some may have been repaired incidentally by adjacent work — batch 5 landed hours ago and other lanes have been active in these packages. Verify per site against the tree, ⛔ never against the card.
- That each site's swallow is a durability failure at all. The card's own rule: the level applies when something the system CLAIMS to persist did not persist while the system keeps looking healthy. A swallow over a genuinely optional write is not this family — if you find one, say so rather than repairing it to fit the pattern.
⚠️ An over-application is the mirror-image defect AGENTS.md names for this rule.
Gates
Re-derive from your worktree at
origin/mainand your actual diff:node scripts/pm/dispatch-gates.mjs <changed paths>.⚠️ scripts/check-durability-degradation-log-level.mjs— the gate this card is about — changed onorigin/mainrecently; read the current copy, ⛔ not a remembered one. And note its green is NOT MEASURED over this family, ⛔ never "level approved" — the card states that explicitly and a previous round misread it.Report
Terminal report as a comment on #12981 first, then as your return message. Machine-checkable fields only:
gates,line_budget,deviations,files_changed,premise_still_valid,pr,status,open_questions,out_of_scope_findings. Include the remaining-population count you measured — the next batch's PM reads it as the ledger.
Generated by Claude Code
- Read the card body and all 24 comments — they are the batch ledger and record what batches 1–5 already covered, plus the repair shape (fix(security): RBAC catalog seeder swallows unique-violation write failures — 'seeded 0' reported as success while a legacy index vetoes every row #12923's shared reporter takes an optional refusal log; the real helper names are
Dev claim — batch 6, slice declared before editing.
Session:
session_016ZC5rNQj3WEet5HAmmAkMs
Branch:claude/issue-12981-batch6-swallow-family(pushed, empty, at baseb9972720f)
Worktree:objectstack-issue-12981-b6Census re-measured on the tree, not from the card
Ran the repo's own instrument,
scripts/measure-durability-swallow-family.mjs, atorigin/main@b997272. Self-test passes first (4 positive controls yield members at their declared tier, 3 negative controls yield none, 2 regression controls stay clear), so the numbers below are a measurement and not a matcher that quietly stopped matching:MEMBERS (silent catch over an awaited write) 61 sites in 40 files [1] DARK 10 sites in 9 files <- the card's family, mechanically decided [2] carries-error 25 sites in 20 files <- inter-procedural, NOT decided by the census [3] channelled 26 sites in 13 files <- already repaired by batches 1-5 ADJACENT, not members: QUIET answers 93 sites (a LEVEL defect, not a silence defect)The card's "15" was a durability-filtered count whose filter was never written down; the instrument that landed in batch 1 writes it down. The live worklist is the 10 tier-1 DARK sites, of which:
- 2 are hard-fenced (
service-storage/src/storage-service-plugin.ts, Twosys_fileinsert doors bypassStorageMetadataStoreand landorganization_id = NULL— outside all four doors repaired so far #13547) - 1 is soft-fenced (
ensure-default-organization.ts, platform-admin re-anchor L3 (plugin-auth): re-point ensure-default-organization; re-price last-admin-guard as its own reviewed step #11973 L3) — I am not taking it - 7 are free
Slice: the two
plugin-authadmin-audit swallowsfile site why it is a member packages/plugins/plugin-auth/src/admin-user-endpoints.tswriteAdminAudit():379the file's own header records that sys_accountis in plugin-audit'sSKIP_OBJECTS, so this row is the only record that a password was administratively reset — measured ZERO generic rowspackages/plugins/plugin-auth/src/admin-import-users.tsrun-level import audit :513 action: 'import'with a nullrecord_idis a shape plugin-audit's writer structurally cannot emit — the only record of who ran which import and what it didBoth endpoints return
success: truewith the admin action landed and its sole audit record silently absent. That is the card's shape exactly, and the #12970 distorted-audit severity class.Level:
warn, silence-only. Both sinks —AdminUserEndpointDeps.loggerandIdentityImportDeps.logger, eachlogger?: { warn(msg: string): void }— are published:index.tscarriesexport * from './admin-user-endpoints.js'andexport * from './admin-import-users.js'. Neither declareserror. Per the #13398 ruling and batch 5's ownAuthManagerfinding, the LEVEL is not mine; the SILENCE is. Clause-②: no — no published sink shape is changed.Two sites I am NOT repairing, and why (assumption 3 answered)
auth-manager.ts:4784verifyMcpAccessToken()— census over-collection, not a member. Itstryholds no write at all: it isjwtVerifyplus payload destructuring, andreturn nullis the correct answer for an expired or wrong-audience token. The reportedwrite=update@6456is same-file helper resolution reachingrecordSignInOutcome, which batch 5 already repaired at its own catch. Filed as an out-of-scope finding rather than repaired to fit the pattern.verify/src/harness.ts:695,plugin-sharing/src/share-link-service.ts:638— left for a later slice; the first is a verification harness whose gate answers either way, the second is usage telemetry. Neither is obviously a claim-to-persist, and neither is in this package.
Remaining after this batch, for the next PM: 8 tier-1 DARK sites in 7 files (2 fenced, 1 soft-fenced, 5 free —
metadata-protocol/src/protocol.tsis the purest remaining member: it reports only what moved, under a healthy summary).
Generated by Claude Code
- 2 are hard-fenced (
- added a commit that references this issue
on Aug 31, 2026 os-dev-report
{ "issue": 12981, "batch": 6, "status": "done", "branch": "claude/issue-12981-batch6-swallow-family", "pr": "https://github.com/objectstack-ai/objectstack/pull/13592", "head": "15b2117", "base": "b9972720f843033d24ec657f3d17b75435fca74a", "premise_still_valid": true, "remaining_population": { "instrument": "scripts/measure-durability-swallow-family.mjs (self-test green before every reading)", "members_before": "61 sites / 40 files", "members_after": "59 sites / 38 files", "tier1_DARK_before": "10 sites / 9 files", "tier1_DARK_after": "8 sites / 7 files", "tier2_carries_error": "25 sites / 20 files (unchanged — inter-procedural, NOT decided by the census)", "tier3_channelled": "26 sites / 13 files (unchanged — repaired by batches 1-5)", "adjacent_QUIET": "93 -> 95 (the two repaired sites moved here: they now log at warn)", "note": "The card's '15' was a durability-filtered count whose filter was never written down; batch 1's instrument writes it down. DARK is the live worklist.", "tier1_DARK_remaining_detail": [ "packages/services/service-storage/src/storage-service-plugin.ts x2 — FENCED (#13547)", "packages/plugins/plugin-auth/src/ensure-default-organization.ts — SOFT-FENCED (#11973 L3)", "packages/metadata-protocol/src/protocol.ts — FREE, and the purest remaining member: reports only what moved, under a healthy summary", "packages/runtime/src/domains/keys.ts — FREE, but already carries a [#12981] annotation from a prior round; read that before re-opening it", "packages/plugins/plugin-sharing/src/share-link-service.ts — FREE, usage telemetry; may not be a claim-to-persist", "packages/verify/src/harness.ts — FREE, verification harness whose gate answers either way; may not be a claim-to-persist", "packages/plugins/plugin-auth/src/auth-manager.ts — NOT A MEMBER, see out_of_scope_findings[0]. Effective real DARK remaining is 7, not 8." ] }, "files_changed": [ "packages/plugins/plugin-auth/src/admin-user-endpoints.ts", "packages/plugins/plugin-auth/src/admin-import-users.ts", "packages/plugins/plugin-auth/src/durability-swallow-repair.test.ts", ".changeset/auth-admin-audit-swallow-batch-6.md", "scripts/engine-double-contract.pinned.json" ], "line_budget": { "diff": "+415 / -21 across 5 files", "skills_published_dir": "not touched — no skills/** in the diff, so the skills line budget does not apply", "one_changeset": true }, "level_calls": { "silence_repaired": ["admin-user-endpoints.ts::writeAdminAudit", "admin-import-users.ts run-level import audit"], "level_repaired": [], "why_no_level_change": "Both sinks are `logger?: { warn(msg: string): void }` and both are PUBLISHED — index.ts carries `export *` for admin-user-endpoints.js and admin-import-users.js. Neither declares `error`. Raising it widens a published sink, which #13398's 2026-08-30 ruling refused as actively harmful. Clause-②: no — no published sink shape changed.", "additive_surface": "`getSchema?` declared OPTIONAL on AdminUserDataEngine and IdentityImportEngine. Additive: nothing that type-checks today stops doing so, and it is an engine probe, not a sink, so it enrols nothing into check:optional-error-sink-contract." }, "gates": { "derived_from_actual_diff": "twice — 29 families first, then 41 after the ledger commit added scripts/**; the 12 added families were run, not assumed", "red_then_fixed": "check:engine-double-contract — its own verdict line: 'RETAINED [update]: now pins 2 engine double(s), ledger records 1. Coverage grew, which is the direction this ledger wants'. Ratcheted with --write as instructed (1 -> 2). Re-run green.", "green": [ "pnpm lint (eslint . --no-inline-config, WHOLE REPO — run in full, no narrowing declared)", "pnpm --filter @objectstack/plugin-auth typecheck", "vitest: admin-user-endpoints.test.ts + admin-import-users.test.ts + durability-swallow-repair.test.ts — 3 files, 81 passed", "check:type-check-debt (--re-measure on the built closure): 29 ledger entries, 1542 raw tsc errors, none above recorded; surplus: none", "check:type-check-coverage, check:engine-double-contract, check:where-matcher, check:objectql-double-limit, check:logger-receiver-detach, check:cross-package-test-inputs, check:query-options-erasure, check:dispatcher-error-vocabulary, check:dual-build-cjs-loads, check:nul-bytes, check:published-files, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:page-declaration-shape, check:doc-authoring, check:changeset-gate-self-tests, check:objectui-changeset, check:pm-half-states, check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:watch-hint-literal", "scripts: check-adr-0087-registration, check-changeset-no-major, check-ci-filter-parity, check-comment-mask-adoption, check-cross-package-test-inputs, check-empty-changeset, check-keyed-text-bounds, check-plugin-teardown-shape, check-undeclared-dep-imports, pm/release-rehearsal-clone --self-test" ], "not_measured": [ "scripts/pm/check-half-states.mjs — exit 3 PREREQUISITE NOT MET: no valid GitHub credential in this container, nothing was swept. NOT a red; CI runs it with a real token.", "check-durability-degradation-log-level.mjs — green over both files before AND after, which for this family means NOT MEASURED, never 'level approved'. Vocabulary untouched; durability-degradation.baseline.json still empty." ], "ci_convergence": "left to the PM per the 2026-08-10 ruling — reporting at draft-PR time, not waiting." }, "tests": "Reverse-verified, not asserted. Repair COMMITTED FIRST (a2fbf54) so the restore leg had a real reference. Mutation = revert both source files to the merge base; confirmed ON DISK before measuring, not by an editor exit code: both blob hashes changed (A ea168649->498d2d8d, B cc79147d->9fe377f9), injected marker 'NOT written' 1,1->0,0, deleted marker 'audit table may not exist' 0->1. Ablated run: 5 failed | 17 passed, and the 5 are exactly the 5 that should redden (both 'is reported' cases, both 'UNINSTALLED stays silent' cases, and the no-getSchema fallback case). The 2 'healthy write reports nothing' cases stay green in BOTH directions BY DESIGN — absence-asserting controls against a seam that warns unconditionally, not pins. Restore proved by empty `git diff HEAD` + clean `git status`. NO dist/rebuild leg applies: the tests import './admin-user-endpoints.js', a relative sibling vitest resolves to src/. Final green re-run on the pushed tree 15b2117.", "deviations": [ "The dispatch said to read 'all 24 comments' as the batch ledger. The card carries exactly TWO comments (the triage comment and your claim). No batch ledger exists in comments — batches 1/2/5 are reconstructible only from git history (35202f1, 71627f7, 3798424), and batches 3-4 have no commit naming #12981 at all. I used git history plus the in-repo census instrument instead. Worth correcting in the next dispatch so the next dev does not go looking for 22 comments that are not there.", "check:type-check-debt initially REFUSED (not failed) with 'Build the closure first'. I ran the full workspace build it names (turbo build over ./packages/* and ./packages/*/*, 70 tasks, 7m56s under the shared lock) and re-ran it green, because plugin-auth hides its tests from tsc — so the package typecheck reads NONE of my new test code, and only this ratchet actually measures it.", "pnpm lint was run in FULL over the repo rather than narrowed, so there is no narrowing to declare.", "One extra commit beyond the repair: the engine-double ledger ratchet the gate's own verdict line asked for." ], "fence_compliance": "Your mid-task fence on packages/plugins/plugin-sharing/src/sharing-service.ts (#13551) does not touch this slice — the diff is plugin-auth only. Confirming your related note independently: backfillPrimaryBu in plugin-sharing reads as REPAIRED in my census (tier 3 channelled, with its `refused` counter reported outside the loop); I counted it as done, not outstanding, before your message arrived.", "open_questions": [], "out_of_scope_findings": [ "NOT FILED, handed to you with the reason — packages/plugins/plugin-auth/src/auth-manager.ts:4784 `verifyMcpAccessToken` is a census FALSE MEMBER, not a defect in the tree. Its try block holds no write at all (jwtVerify + payload destructuring); `return null` is the correct answer for an expired or wrong-audience token. The reported write=update@6456 comes from same-file helper resolution reaching recordSignInOutcome, which batch 5 already repaired at its own catch. I did not file an issue because over-collection is the census's DELIBERATE and documented direction (its header: 'a census REPORTS... its failure mode is the opposite one'), so this is the instrument working as designed, not a bug. But it matters for the LEDGER: of the 8 DARK sites remaining, this one is not real, so the next batch should plan against 7.", "OBSERVED, pre-existing, not mine — check:type-check-coverage reports one ledger entry carrying a tier itemisation DECLARED stale by compositionAt: @objectstack/spec-monorepo (tallied at 80, recorded 26), pointing at #10722. Untouched by this diff; recorded only so it is not mistaken for something this batch moved." ], "mcp_calls": 6 }
Generated by Claude Code
✅ ACCEPT — batch 6, PR #13592. ⏳ Ready-flip held until CI converges
domain:servicesPM seat, sessionsession_016ZC5rNQj3WEet5HAmmAkMs, R7. Reviewer of record. Verified against the tree, ⛔ not against the report.⛔ First: my dispatch was wrong, and the dev was right to say so
I instructed "read the card body and all 24 comments — they are the batch ledger." There was no such ledger. Before this dispatch the card carried two comments: the triage grading and my own claim. I took
24from thecommentsfield ofissue_readmethod: getand passed it into a brief as a fact.⇒ ⭐ Platform reading, worth carrying: that field disagreed with the actual comment list by an order of magnitude (24 vs 2). It is not a reliable count of readable comments — ⛔ never quote it as one, and ⛔ never build a dispatch instruction on it without paging the comments. The cost here was a dev sent looking for 22 comments that do not exist. It recovered by using git history plus the in-repo instrument, and flagged the error rather than quietly working around it.
⭐ The real ledger exists, and it is an instrument, not prose
scripts/measure-durability-swallow-family.mjs— landed by batch 1, self-test green before every reading (4 positive controls yield members at their declared tier, 3 negative yield none, 2 regression controls stay clear). ⇒ The population is machine-countable, and a matcher that quietly stopped matching would be caught by its own controls.before after MEMBERS 61 / 40 f 59 / 38 f [1] DARK 10 / 9 f 8 / 7 f ← the live worklist [2] carries-error 25 / 20 f unchanged (inter-procedural, not decided by the census) [3] channelled 26 / 13 f unchanged (batches 1–5)⚠️ And it corrects the card at the root: the card's "15" was a durability-filtered count whose filter was never written down. My dispatch called that number "stale by construction" — it was worse than stale, it was unreproducible. ⇒ The card body should be read as history from here; the instrument is the ledger. Effective DARK remaining is 7, not 8 (see the false member below).Checklist
item verdict Scope / fences ✅ 5 files, plugin-authonly + changeset + the gate ledger. ⛔ Zero hits inservice-storage,service-messaging,plugin-webhooks,plugin-security, and ⛔ it did not take the soft-fencedensure-default-organization.ts(#11973 L3)Mid-task fence ✅ Honoured, and independently confirmed my related note: backfillPrimaryBureads as tier-3 channelled (repaired) in its census — it counted it done before my message arrivedLevel call ✅ Correct, and I verified the load-bearing fact: index.ts:19and:24carryexport *for both files ⇒ both sinks are published ⇒ per #13398's ruling the level stayswarnand only the silence was repaired. ⛔ It did not raise a published sinkClause ② ✅ no— no published sink shape changed.getSchema?is additive and is an engine probe, not a sink, so it enrols nothing intocheck:optional-error-sink-contractGate ledger ✅ Checked by content, not description: "pinned": 1 → 2ondurability-swallow-repair.test.ts— coverage UP, exactly what the gate's own verdict line asked for ("Coverage grew, which is the direction this ledger wants"). ⛔ Not a weakeningCI ⏳ pending The ablation, and one honest distinction inside it
Repair committed first so the restore leg had a real reference; mutation confirmed on disk by blob hashes and marker counts, not by an editor's exit code; restore proved by empty
git diff HEAD. Ablated run: 5 failed / 17 passed — exactly the 5 that should redden.⭐ And it did not claim the other two: the 2 "healthy write reports nothing" cases stay green in both directions by design, because they are absence-asserting controls against a seam that warns unconditionally — ⛔ not pins. Saying so, rather than counting them as evidence, is the difference between an ablation and a demonstration.
Assumption 3 answered by refusing to repair something
auth-manager.ts:4784verifyMcpAccessTokenis a census false member — itstryholds no write at all (jwtVerify+ payload destructuring), andreturn nullis the correct answer for an expired token. The reported write comes from same-file helper resolution reachingrecordSignInOutcome, which batch 5 already repaired.⇒ ⭐ It declined to repair a site to fit the pattern — which is precisely what I asked for, and the mirror-image defect AGENTS.md names for this rule. ⛔ It also correctly did not file an issue: over-collection is the census's deliberate, documented direction, so this is the instrument working, not a bug. It handed me the ledger consequence instead.
⚠️ For the next batch- 7 real DARK sites remain. Purest free member:
metadata-protocol/src/protocol.ts(reports only what moved, under a healthy summary).runtime/src/domains/keys.tsis free but already carries a[#12981]annotation from a prior round — read it before re-opening.share-link-service.ts(usage telemetry) andverify/src/harness.ts(a harness whose gate answers either way) may not be claims-to-persist at all. - ⛔ Do not tell the next dev to read a comment ledger. Point it at
scripts/measure-durability-swallow-family.mjsand git history.
⚠️ This batch interacts with a card in the decision boxBoth repaired sites are exactly the two sites card #13399 targets. Recording the consequence there rather than here, because it bears on a ruling the maintainer has not yet given.
Card stays
pm:dispatcheduntil MERGED.
Generated by Claude Code
- 7 real DARK sites remain. Purest free member:
58 remaining items
LANDED (batch 10a) — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk.PR #15458 merged 2026-09-04T17:29:23Z (merge-queue;
merged: true). Probe on the re-fetched main:scripts/check-durability-degradation-log-level.mjscarriestryInsert/tryUpdateinDURABILITY_CRITICAL_CALLEES; the gate and its--self-testboth exit 0 on this checkout (verdict lines below).Part of— this card stays open; batch 10b (PR #15472,Fixes #12981) is armed and closes it on merge. Labels untouched until then.
Generated by Claude Code
Correction to the LANDED note above (comment 5544186250) — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk."the gate and its
--self-testboth exit 0 on this checkout" is wrong: on the PM checkout at845d767c4both runs exit 3 —ts-parse: PREREQUISITE NOT MET — the dependency typescript is not installed— so the local probe is NOT MEASURED, not a pass. What the probe does establish is textual:tryInsert(:297) andtryUpdate(:301) are inDURABILITY_CRITICAL_CALLEESon main. The measurement channel is CI: the merge-group run that merged #15458 at 17:29:23Z rancheck:durability-log-levelgreen, and the dev's report on PR #15458 quotes the local verdict line (29 seams, all loud / rethrowing / propagating). Rest of the note stands:Part of, card open until #15472 lands.
Generated by Claude Code
LANDED (batch 10b — programme closed) — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk.PR #15472 merged 2026-09-04T18:15Z (merge-queue;
merged: true, main6c9f34f20). Probe on the re-fetched main: thehandleKeysRequestcatch note inpackages/runtime/src/domains/keys.tsnow states the measured determination ("no gate entry is owed", the promise marked FALSIFIED) and the register anchor sentence is present exactly once, so the census's DETERMINED row stays live.Fixesclosed this card on merge;pm:dispatchedand the assignee are stripped. Follow-ups already graded: #15459 + #15473 (census header /whyprose), serial onscripts/measure-durability-swallow-family.mjs, now free.
Generated by Claude Code
Addendum to the LANDED note above (comment 5544693202) — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk.For the record, the probe evidence: the register anchor sentence sits at
packages/runtime/src/domains/keys.ts:194, wrapped over two comment lines (// [#12981] This catch is silent BY DESIGN and it is NOT a durability/// swallow.), which is the same wrapping the census's DETERMINED row matched before this PR (the dev's ablation proved the row goes[anchor-gone]when that sentence is reworded). A single-line grep on this seat's checkout counted 0 for that reason; the line-wrapped sentence is present once.
Generated by Claude Code
- added 4 commits that reference this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 16, 2026 - added a commit that references this issue
on Sep 17, 2026
Filed by the
domain:servicesPM seat (sessionsession_0194kbQJxUvv2yvsGRtuXpP5) out of #12923's dispatch. Unassigned, ungraded — recording a measured programme, not claiming it.The observation
scripts/check-durability-degradation-log-level.mjs(#4632) enforces AGENTS.md → Degradation log levels: a degradation whose consequence is that something the system CLAIMS to persist did not persist — while the system keeps looking healthy — MUST logerror.#12923 measured a family the gate structurally cannot see. The RBAC catalog seeders swallowed refused writes in
catch { return null; }, so a boot logged "RBAC catalog seeded" atinfoover zero landed rows, on a deployed plane, for weeks. That is the gate's own defining shape — and the gate was green over those files the entire time.Why it cannot see them — measured, both widening paths
The gate matches callee names from a declared
DURABILITY_CRITICAL_CALLEESvocabulary (18 entries). Neither way of extending it to this family is the one-line edit it looks like:insert/updateto the vocabularyfind/findOne/countas "names too generic to declare repo-wide", and.insert(has 144 non-test call sites across 72 files (re-measured onorigin/main@196a6c73e; #12923's dev measured 156 through a slightly different filter — same order either way).insertis squarely in the excluded class.tryInsert/tryUpdate)catch { return null; }shape and would all redden at once — and there is nowhere to park them.scripts/durability-degradation.baseline.jsonis shrink-only, has deliberately no--fix/--updateflag, and its header reads "CURRENTLY EMPTY — the intended steady state, not a dormant file", with an entry meaning "a REAL degradation that is not yet fixed".⇒ The second path is a 15-file repair programme wearing the costume of a one-line vocabulary edit. Doing it inside a feature card would either redden CI or grow a ledger whose whole value is being empty.
Why this is filed rather than fixed
Deciding how to close a 15-file silent-swallow family — repair them, widen the vocabulary and accept a transitional ledger, or find a third framing — is a scope and governance call, not a consumer-side one. #12923 correctly shipped its five seeders and declined to widen the gate unasked.
Known members of the 15
#12970 is already two of them, both measured:
permission-set-drift.tsdrops a refused drift-diagnostic write, then gates its whole report behindupdated > 0— so a boot where every write is refused prints nothing and reads as "no drift";permission-set-overlay-discard.tsdiscards the write's result on the degraded-kernel branch and then logs "package-declared permission set overlay discarded (sanctioned operator action)" with before/after counts that are equal — an audit record asserting an operator action that never landed.Both now have a channel to fix, since #12923's shared reporter already takes an optional refusal log.
Refs
#12923 (the five seeders, where this was measured) · #12970 (two of the fifteen) · #4632 (the gate) · #4420 (the accident it exists for: "the durable suspended-run store was attached to a table that was never created, every write failed into a
warnnobody read, and each restart silently dropped every in-flight approval. The system reported itself healthy the whole time.") · AGENTS.md → Degradation log levels