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
[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)
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)