You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] Publish-time lint for a chained controlled_by_parent declaration (direction 3 of #11082) — and its value CHANGED once direction 1 was implemented #11187
Filed unassigned — recording, not claiming. The #11082 seat identified this and was rate-limited out of its own duplicate search, so it correctly refused to file. The PM is filing it on its behalf, having run that search (#7503 is the no-relation case and is already implemented as SECURITY_CBP_NO_RELATION; #9139 is the master-detail-required promotion — neither is this).
⚠️Read the value section before grading this. The finding as the seat first conceived it no longer applies. Grading it on its original premise would over-value it.
What direction 3 was, when it was written
#11082's triage carved out three directions. Direction (1) — make the runtime derivation compose across a chain — is implemented in PR #11183 (still draft, needs:contract-review unresolved; not merged as of this filing). Direction (3) was a publish-time lint: refuse, or warn on, an authored object that declares sharingModel: controlled_by_parent whose master is itselfcontrolled_by_parent.
At the time it was written, direction 3 carried a safety argument: if the runtime does not compose the chain, then a chained declaration is a shape whose metadata reads as narrowed while the runtime leaves it unnarrowed, and refusing the declaration at publish time is the only thing standing between an author and that state.
Why its value changed
Once #11183 lands, that safety argument is gone. The runtime composes the chain, and both guards fail closed. A chained declaration is then a supported shape, not a trap — so a lint that refuses it would be removing a capability the same day it was added, and a lint that merely warns would be warning about something correct.
What remains is genuinely smaller, and is a cost / authoring-signal argument rather than a safety one:
Each additional hop costs one more master-id resolution per hop, per request on the read path, and one more master-row edit check on the write path. The bound is CBP_MAX_CHAIN_DEPTH = 8 (packages/plugins/plugin-security/src/security-plugin.ts:360), and past it the walk denies — fail-closed by design.
Everything shallower than the bound is a cost curve, not an error. Whether the repo wants to signal on it at all is a judgement call about how loud a lint should be about performance.
Measured in this repo (from PR #11183's re-derivation): every authored controlled_by_parent object today has a chain of exactly one hop, and in each case the master is public_read_write or private — never itself derived. So the population this lint would speak to is currently empty. That is an argument for filing it and not doing it yet, which is what this card is.
Where it would land, if graded as worth doing
packages/lint/src/validate-security-posture.ts, next to SECURITY_CBP_NO_RELATION (:74, emitted at :326). That rule already walks the same declaration and already knows how to find the master relation, so the chain walk is an extension of an existing traversal rather than a new one. Note packages/lint/scripts/check-doc-security-posture.mjs:346 special-cases SECURITY_CBP_NO_RELATION when the fields subtree is incomplete — any sibling rule that reads the same subtree inherits that concern and should be checked against it rather than assumed independent.
⚠️ This card is laned domain:services because the semantics are this lane's. The file surface is packages/lint/, which this lane does not otherwise own. If triage judges lint-package ownership sits elsewhere, re-lane it — that is a correction, not a dispute.
Possible gradings (not a recommendation — this wants grading)
Won't do. The runtime composes and fails closed; the bound denies loudly and logs the chain it refused. A lint adds a second place to keep in sync with the constant, for an empty population.
Depth-bound only. Refuse at publish time exactly what the runtime would refuse at request time — a chain longer than CBP_MAX_CHAIN_DEPTH. Narrow, mechanical, and the constant becomes a shared fact that must not drift; that shared fact is itself the cost.
Premise this card rests on, stated so it can be checked
Direction (1) is not merged at filing time — PR #11183 is draft pending contract review. If that PR does not land, or lands narrowed, the safety argument above comes back and this card should be re-graded on its original premise rather than this one.
Refs
#11082 (the card; directions 1/2/3) · PR #11183 (direction 1, draft) · #7503 (the no-relation lint, the sibling this would sit beside) · #9139 (relationship/master-detail-required promotion, ruled for the v18 boundary) · ADR-0055 (and see the companion finding on its single-level-only text)
Finding concentration round (triage seat, session session_01Kktexqp6uVuFMztvvTMf3V, 2026-08-24, maintainer-invoked): graded → pm:blocked (state swap in the same write, finding off). The card itself warns its original safety premise dissolves if direction 1 (PR #11183, runtime chain composition) lands — that PR is draft with needs:contract-review unresolved, so the value of a publish-time lint is undecidable until it settles.
Blocked-by:#11082 Unlock-action: when #11082 closes, re-grade: if #11183 landed the composing derivation, this becomes at most an advisory-lint Task (or a close); if that direction died, the safety argument revives and this queues as written.
Unblocked (skills seat, session 5213b871-5164-5bc3-8874-28b336bbcd40; basis: the maintainer-instructed 2026-08-24 pm:blocked census, #11747 — upstream #11082 is closed on an earlier day, and this card's Blocked-by: lived in a comment with the backticked-key spelling, one of the four live forms the strict scan predicate cannot see; both defects chartered on #11747).
pm:blocked → pm:queue. Claimer re-verifies premise and file surface on the current merged ref, and checks for a NEW blocker before dispatching (the #11152 exemplar).
Maintainer ruling — grading 1: WON'T DO; closing not planned
Source: maintainer, 2026-08-25, live PM chat (decision-inbox batch 1 review, session session_01KWRU3s15AJz7PGW7a7wdCh), verbatim: 「接受你的建议,继续」 — accepting the presented recommendation for this card: grading 1 (won't do).
Grounds, as presented and accepted: the runtime composes the chain and fails closed (PR #11183 merged); a chain deeper than CBP_MAX_CHAIN_DEPTH is refused loudly at request time with the refused chain logged; the population an authored-chain lint would speak to is measured empty in this repo; and a publish-time depth lint would turn the depth constant into a two-site shared fact with a permanent anti-drift obligation — a startup-scope cost with zero pull today.
The lane question raised by the card body is answered as moot, with the answer recorded for any future reopen: by the anchoring rule (domain = the package the fix lands in), a lint in packages/lint/ is domain:devx, whatever lane owns the semantics.
Reopening is free and its premise is stated: if authored controlled_by_parent chains ≥2 hops appear in real apps (the population stops being empty), re-grade between gradings 2 (depth-bound only) and 1 on that day's measurements. The implementation clue (extend the SECURITY_CBP_NO_RELATION traversal at packages/lint/src/validate-security-posture.ts:74/:326, and check the check-doc-security-posture.mjs:346 special-case) is preserved in the 2026-08-24 19:59Z comment.
Closing not planned; needs-user-decision off in the same write.
Filed unassigned — recording, not claiming. The #11082 seat identified this and was rate-limited out of its own duplicate search, so it correctly refused to file. The PM is filing it on its behalf, having run that search (
#7503is the no-relation case and is already implemented asSECURITY_CBP_NO_RELATION;#9139is themaster-detail-requiredpromotion — neither is this).What direction 3 was, when it was written
#11082's triage carved out three directions. Direction (1) — make the runtime derivation compose across a chain — is implemented in PR #11183 (still draft,
needs:contract-reviewunresolved; not merged as of this filing). Direction (3) was a publish-time lint: refuse, or warn on, an authored object that declaressharingModel: controlled_by_parentwhose master is itselfcontrolled_by_parent.At the time it was written, direction 3 carried a safety argument: if the runtime does not compose the chain, then a chained declaration is a shape whose metadata reads as narrowed while the runtime leaves it unnarrowed, and refusing the declaration at publish time is the only thing standing between an author and that state.
Why its value changed
Once #11183 lands, that safety argument is gone. The runtime composes the chain, and both guards fail closed. A chained declaration is then a supported shape, not a trap — so a lint that refuses it would be removing a capability the same day it was added, and a lint that merely warns would be warning about something correct.
What remains is genuinely smaller, and is a cost / authoring-signal argument rather than a safety one:
CBP_MAX_CHAIN_DEPTH = 8(packages/plugins/plugin-security/src/security-plugin.ts:360), and past it the walk denies — fail-closed by design.sharingModel: controlled_by_parentwith nomaster_detailrelation is statically detectable and unreported #7503.Measured in this repo (from PR #11183's re-derivation): every authored
controlled_by_parentobject today has a chain of exactly one hop, and in each case the master ispublic_read_writeorprivate— never itself derived. So the population this lint would speak to is currently empty. That is an argument for filing it and not doing it yet, which is what this card is.Where it would land, if graded as worth doing
packages/lint/src/validate-security-posture.ts, next toSECURITY_CBP_NO_RELATION(:74, emitted at:326). That rule already walks the same declaration and already knows how to find the master relation, so the chain walk is an extension of an existing traversal rather than a new one. Notepackages/lint/scripts/check-doc-security-posture.mjs:346special-casesSECURITY_CBP_NO_RELATIONwhen thefieldssubtree is incomplete — any sibling rule that reads the same subtree inherits that concern and should be checked against it rather than assumed independent.domain:servicesbecause the semantics are this lane's. The file surface ispackages/lint/, which this lane does not otherwise own. If triage judges lint-package ownership sits elsewhere, re-lane it — that is a correction, not a dispute.Possible gradings (not a recommendation — this wants grading)
CBP_MAX_CHAIN_DEPTH. Narrow, mechanical, and the constant becomes a shared fact that must not drift; that shared fact is itself the cost.Premise this card rests on, stated so it can be checked
Direction (1) is not merged at filing time — PR #11183 is draft pending contract review. If that PR does not land, or lands narrowed, the safety argument above comes back and this card should be re-graded on its original premise rather than this one.
Refs
#11082 (the card; directions 1/2/3) · PR #11183 (direction 1, draft) · #7503 (the no-relation lint, the sibling this would sit beside) · #9139 (
relationship/master-detail-requiredpromotion, ruled for the v18 boundary) · ADR-0055 (and see the companion finding on its single-level-only text)