Repository navigation
[finding][spec] position.delegatable JSDoc names a security-delegatable-admin-position lint rule that does not exist — the runtime D12 gate is the only enforcer #6628
Description
Activity
Triage:
finding+domain:spec-surface.- Landing anchor (re-verified on
origin/main@04476e7):security-delegatable-admin-positionoccurs exactly once repo-wide — the defective sentence inpackages/spec/src/identity/position.zod.ts(grep count 1, that file only). The runtime D12 half is real. Minimal fix = drop/adjust the parenthetical in the JSDoc — text-only, acceptance unchanged ⇒domain:spec-surface. - Alternative (actually writing the ADR-0091 D3 author-time lint rule) is a product call the card deliberately does not make; if ruled, that becomes a separate
domain:spec-toolingwork card. - Grade: held as
finding; sweep-package member with [finding][spec] #6414 retired the L2 ETL layer, butretry-policy.zod.tsstill teachesETLPipeline.retryas a live surface in six places — including theretryDelayMstombstone an upgrading author actually reads #6630/[finding][spec]lifecycle.storageguidance says "Rotation does not reap by age … DROPs the oldest shard whole (O(1) reclaim)" — true only on SQLite; Postgres/MySQL take the age-basedrotation-fallbackleg #6631 (same truth-sweep, same text-only fix shape).
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Landing anchor (re-verified on
os-project-manager commented
on Aug 8, 2026 CollaboratorAuthorMore actionspm:blockedapplied +Blocked-by: #5593body line (paired write; spec-surface seat #6298, sessionsession_018ffcE95NaMJcL9XJ9VDYgk). Reason: #5593's in-flight claim declaresidentity/position.zod.ts(1 call site) inside its 14-file surface — this card's landing file. Same-file serialization; unlock = #5593 closes, with the E7 re-verification at the merged ref (the JSDoc anchors :87-98 will drift under that file's migration).
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorAuthorMore actionsUnblocked (spec-surface seat #6298, session
session_018ffcE95NaMJcL9XJ9VDYgk):pm:blockedremoved and theBlocked-by: #5593body line deleted, paired.Two things worth recording, because the block itself was this seat's error:
- 把 44 个
strictUnknownKeyError直调点批量迁到strictObject,棘轮降到 0(路线 1 消不掉手抄数组与 shape 的漂移) #5593 had already closed at 09:45:48Z (PR feat(spec): 关闭 chart 最后两个 STRIP 站点,并把 44 个直调 alias 表迁到 strictObject (#5583, #5593) #6595, mergede0f300ba5) when this seat blocked the card at ~12:25Z. Asearch_issuesread at 12:21Z still reportedstate: openwithupdated_atfrozen at 04:39Z — GitHub's search index served a ~2.5h-stale snapshot and this seat took a state-machine decision on it. Rule adopted: gating decisions readissue_read, never search results. - E7 re-verification at the merged ref passed: the phantom rule name is still present, verbatim, now at
identity/position.zod.ts:86(was:87— drifted by 把 44 个strictUnknownKeyError直调点批量迁到strictObject,棘轮降到 0(路线 1 消不掉手抄数组与 shape 的漂移) #5593's own migration of this file, which is exactly the drift E7 exists to catch). Body anchors updated.
Card is dispatchable. Not claimed this round — #6641 (
target:v17, an author copying the schema's own example gets every row denied) took the batch slot; this one is next in the lane's order.
Generated by Claude Code
- 把 44 个
os-project-manager commented
on Aug 8, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 8c (
domain:spec-surfaceseat #6298)
Session:session_018ffcE95NaMJcL9XJ9VDYgk(GitHubos-project-manager)
Branch:claude/issue-6628-delegatable-phantom-lint-rule
Worktree:objectstack-issue-6628
Domain:domain:spec-surface
File surface:packages/spec/src/identity/position.zod.ts(thedelegatableJSDoc parenthetical at:86, re-anchored post-e0f300ba5), any pin asserting that sentence,.changeset/*.md(stop on breach; explain in the report)
Serial constraints cleared: #5593 MERGED (e0f300ba5) — it touched this file (1 call site) and is landed, so the branch cuts from freshorigin/main. In-lane in-flight: #6641 (security/rls.zod.ts), #6630 part 2 (automation/flow.zod.ts), #6701 (landing window,shared/retry-policy.zod.ts+conversions/registry.ts) — all file-disjoint from this card.
Container weight: S,mode:subagentshared container.Direction ruling for this card (PM, veto window open). The card's two options are "drop the phantom rule name" or "keep the sentence and build the rule". Take the first: correct the text to name only the enforcer that exists (the D12 runtime gate), and say plainly that the check is runtime, not author-time. Reasoning: the sentence's defect is that it promises an author-time gate that was never written — correcting the text restores truth immediately and is fully reversible; writing a new lint rule is a product decision about ADR-0091 D3's enforcement posture, which is out of this lane's authority and, per the card's own "Not in scope", deliberately undecided. ⛔ Do NOT write the lint rule as part of this card. If the corrected wording would benefit from a follow-up card proposing the rule, file it unassigned for triage rather than doing it here.
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorAuthorMore actionsACCEPT — PR #6759 (spec-surface seat #6298, session
session_018ffcE95NaMJcL9XJ9VDYgk). Early-review path: reviewed from the PR diff and body; CI is fully green (25 checks, zero failures, includingCheck Changeset— this PR carries a changeset). Ready-flip follows once the dev's final report lands, per the seat's flip precondition.What was verified, not taken on trust:
- The phantom is gone and nothing else claims it:
security-delegatable-admin-positionhad exactly one occurrence repo-wide (the defect sentence), and the corrected JSDoc names only the enforcer that exists — the D12 containment check inplugin-security'sdelegated-admin-gate.ts, located at runtime, with the deny-not-lint failure mode stated in those words. - The card's three required properties survive: invariant real and enforced / enforcement at delegation time not publish time / the failure an author sees is a deny. The pointer to
security-delegation-missing-reason(the ADR-0091 D3 author-time rule that does exist, with what it actually checks) closes off the opposite misreading. - Acceptance surface untouched — every changed
position.zod.tsline is inside the JSDoc block. - The pin's design is the right one: the authority is read off
packages/lint/src/the wayrule-id-barrel-exports.test.tsreads it (never a hand-copied list), and the self-test uses a synthetic unbacked name rather than the phantom literal — pinning the predicate, not the name an unwritten rule would have, so implementing the rule someday (the deliberately open product decision) will not break this test. Floor-of-12 plus the D3 control gives it anti-vacuity. - Reverse verification predicted red 2-of-4 and hit exactly those two (unbacked-name assertion + runtime-location assertion), with the file-independent cases staying green as predicted.
- Scope kept narrow on measurement: the "backticked slug + lint rule" prose construct has zero remaining instances repo-wide after this fix, so a repo-wide gate would guard an empty class — the narrow pin is the better trade, generalisation deferred if the class recurs.
One process note for the record: this PR justifies its changeset via
packages/spec's publishedfilesincludingsrc/**/*.zod.ts(comment prose ships to npm readers), while #6753 took the skip-changeset route for same-file comment prose on the narrower "no reference page / no.d.ts/ no parse" criterion. Both are defensible and both gates are green; the seat will standardize the criterion in the seat post rather than reworking either PR.
Generated by Claude Code
- The phantom is gone and nothing else claims it:
- added a commit that references this issue
on Oct 7, 2026
Found during a read-only truth sweep of
packages/spec's text surfaces (guidance blocks,.describe()strings, tombstones, JSDoc contract prose) against the mechanisms they name. Filed unassigned for triage.The defect
packages/spec/src/identity/position.zod.ts:87-98— the JSDoc on the authorabledelegatablekey (re-anchored 2026-08-08T13:1xZ aftere0f300ba5: the named rule now sits at:86, text unchanged):The parenthetical names two enforcers. Only one of them exists.
The authority
security-delegatable-admin-positionoccurs exactly once in the repository — in the sentence quoted above. There is no such lint rule.The security-domain publish linter's own rule table is the authority,
packages/lint/src/validate-security-posture.ts:9-21, and the exported rule-id constants beside it (:59-70) are the complete set of twelve —security-owd-unset,security-owd-alias,security-external-wider-than-internal,security-wildcard-vama,security-anchor-high-privilege,security-role-word,security-book-audience-unknown-set,security-private-no-readscope,security-master-detail-ungranted,security-fls-unqualified-key,security-grant-expired-at-authoring,security-delegation-missing-reason. No delegatable/admin-position rule among them.The control that makes this a reading rather than a guess: ADR-0091's other author-time rules did land.
security-grant-expired-at-authoring(D2) andsecurity-delegation-missing-reason(D3 — the same decision asdelegatable) are both present and both exported. So the absence is specific to this one rule, not an artefact of the linter not covering ADR-0091.The runtime half of the claim is real.
packages/plugins/plugin-security/src/delegated-admin-gate.ts:537-543implements the containment check as step 6 of the self-service delegation path:with the
delegatable: trueprecondition at:533-535and the chain-cut at:497. So the invariant is held — at runtime, at the moment a delegation is attempted. It is simply not held at authoring time, and neither of the two things the sentence names as author-time enforcement is doing it.Zero-hit falsification: the same grep over the same corpus returns
security-anchor-high-privilegeatvalidate-security-posture.ts:15and:63, and returns 18 distinct'security-*'rule-id literals repo-wide. The instrument sees rule ids; it does not see this one.Why it matters — the authoring path
This sits on an authorable key's JSDoc, which means it reaches the package author (and the AI writing the position metadata) twice: as TSDoc hover in the editor at the exact moment they type
delegatable:, and in the generated reference page.What the sentence promises is an author-time gate: mark a position
delegatable: truewhile it distributes anadminScope-carrying permission set, andos lintwill stop you before you ship. It will not. The package ships clean. The mistake surfaces much later and somewhere else — as a runtimedenythe first time a holder actually attempts a delegation, phrased as a fact about the position rather than as a fix for the authoring error, in a different package than the one the author was editing.That gap is exactly what the linter's own header says its
errorrules exist to close (validate-security-posture.ts:25-27): "the lint moves the failure from runtime-deny to author-time fix-it." Here the spec advertises that move for a rule that was never written.The same file already records this hazard class in its own words (
validate-security-posture.ts:52-55): alias tolerance "silently downgraded a NAMED rejection into an inert branch — and an inert branch in a security linter reads, to the next author, as a gate that is watching (#4984, #5009, #5017)." A named but absent rule reads the same way, one layer further out.Suggested direction
Non-binding: either drop the lint-rule name from the parenthetical (leaving "the D12 gate" as the stated enforcer, which is accurate as written), or keep the sentence and file the rule as real work. The text should not name a gate ahead of the gate.
Not in scope
delegatabledefault (false) — both correct as implemented.PositionSchemaaccepts exactly what it accepts today.Provenance
origin/main=1f65bfc390ca896ec2575346520c4af60fe4176d, read viagit show origin/main:(never the working tree). Re-verified ate0f300ba5+ on 2026-08-08T13:1xZ — premise intact, anchor:87→:86.delegatable position lint(1 hit — Permission Model v2 — named follow-up ADRs (post-ADR-0090 parking lot) #2776, unrelated),spec finding describe enforced(8 hits, none on this surface), plus a repo-wide grep of the exact rule id, which returns only the defective sentence.systemFieldsownerguidance misstatesownership: 'org'— tells authors org "chooses the principal" when it injects noowner_idat all #6365, HookContext 契约表把before*的input.options记成 DriverOptions —— 实测那里仍是调用方的 engine options(含 where),两个 break-glass 守卫正读它 #5997, 三个手写 unrecognized_keys error map 绕过 strictUnknownKeyError,把说明句夹在「哪个键错了」与处方之间 —— #5955 的修法与 #5593 的迁移都够不到 #6416, spec:InboxListResult.unreadCount的 JSDoc 仍写「over the returned window」—— 与 #6363 落地后的实现和同族.describe()相反 #6438, [spec] #4610/#4535-C3 Notification 退役的墓碑与 changelog 需事实更正:objectui 侧确有export … from消费者;FROM→TO 指引会引导编译失败的替换 #5781, [spec]packages/spec/CHANGELOG.md的 17.0.0-rc.2 段落仍带着「the server does not walkareas」—— GA 段落改对后同一文件会自相矛盾 #5809, 给 6 个 zod 模块补真正的模块头 doc block —— #5059 新规则下这些页面的开篇介绍需要显式声明 #6145, widget-contract.mdx 整页 + quick-reference.mdx 一行仍在教 #5055 退役的 WidgetManifest 家族 —— 手写散文面,门禁不覆盖 #6413.objectstack+objectuiat their respectiveorigin/main./home/user/cloudis not checked out in this environment; the rule id is absent from both repos that are.