Blocked-by: #17516
Unlock-action: re-check PR #18022
A package's entire declared capability is dropped on a capability-name collision, and the drop is invisible to the author who caused it.
packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts, the else arm at the skippedForeign branch (anchor by content — out.skippedForeign += 1), on origin/main 32a321430:
} else {
out.skippedForeign += 1;
logger?.warn?.('[security] capability name owned by another package — skipped', {
name: cap.name, declaredBy: packageId, ownedBy: existing.package_id,
});
}
Why this is a finding
This is the same shape as the permission-set defect on the sibling card #17516, one axis over, and it was found while delivering that one. logger?.warn?. is optionally chained twice, so a caller that passes no logger produces no output at all — the declared capability vanishes with only an internal counter incremented.
⛔ The skip itself is correct under ADR-0086 D4 (a package never writes into a foreign record) and this card does not propose changing it. The defect is that the refusal never reaches the author.
Measured, with a lit control
Driven through bootstrapDeclaredCapabilities with a foreign-owned sys_capability row and no logger passed, spying on all five console channels:
skippedForeign = 1 (the declared capability was dropped)
author-visible console lines = 0 (log, info, warn, error, debug — all five)
LIT CONTROL, same harness and same measurement on the permission-set axis after its repair landed: skippedForeign = 1, author-visible console lines = 1. So the zero above is a reading, not an artefact of the probe.
The repo's own existing pins on this branch (bootstrap-seed-round-trips.test.ts) all pass a logger, so none of them exercises the no-logger path.
Why it is a separate card rather than a rider
The permission-set fix was fenced to its own three lines. The capability axis needs its own diagnostic record type, its own key on the capability seeder's outcome, and its own pins — a second design rather than a mechanical copy — so riding it in would have widened that card's verification surface.
The shared machinery is already exported from the same package and should be reused rather than re-derived: permissionSetNameIsForeign, permissionSetNameCollisionDiagnostic and formatPermissionSetNameCollisionDiagnostic in packages/plugins/plugin-security/src/permission-set-name-collision.ts. The owner-comparison predicate is axis-independent; only the wording and the record are not.
Dedup
Searched before filing, one targeted query, with the channel proven live by the subject itself returning first. Nearest hits, none of them this: #11096 (batching of the same seeder, closed), #4967 (a refused capability declaration suppressing the back-compat derivation, closed). No open card covers this.
⛔ Ungraded — triage sets type and priority. Filed unassigned.
Generated by Claude Code
Blocked-by: #17516
Unlock-action: re-check PR #18022
A package's entire declared capability is dropped on a capability-name collision, and the drop is invisible to the author who caused it.
packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts, theelsearm at theskippedForeignbranch (anchor by content —out.skippedForeign += 1), onorigin/main32a321430:Why this is a finding
This is the same shape as the permission-set defect on the sibling card #17516, one axis over, and it was found while delivering that one.
logger?.warn?.is optionally chained twice, so a caller that passes no logger produces no output at all — the declared capability vanishes with only an internal counter incremented.⛔ The skip itself is correct under ADR-0086 D4 (a package never writes into a foreign record) and this card does not propose changing it. The defect is that the refusal never reaches the author.
Measured, with a lit control
Driven through
bootstrapDeclaredCapabilitieswith a foreign-ownedsys_capabilityrow and no logger passed, spying on all five console channels:LIT CONTROL, same harness and same measurement on the permission-set axis after its repair landed:
skippedForeign = 1, author-visible console lines = 1. So the zero above is a reading, not an artefact of the probe.The repo's own existing pins on this branch (
bootstrap-seed-round-trips.test.ts) all pass a logger, so none of them exercises the no-logger path.Why it is a separate card rather than a rider
The permission-set fix was fenced to its own three lines. The capability axis needs its own diagnostic record type, its own key on the capability seeder's outcome, and its own pins — a second design rather than a mechanical copy — so riding it in would have widened that card's verification surface.
The shared machinery is already exported from the same package and should be reused rather than re-derived:
permissionSetNameIsForeign,permissionSetNameCollisionDiagnosticandformatPermissionSetNameCollisionDiagnosticinpackages/plugins/plugin-security/src/permission-set-name-collision.ts. The owner-comparison predicate is axis-independent; only the wording and the record are not.Dedup
Searched before filing, one targeted query, with the channel proven live by the subject itself returning first. Nearest hits, none of them this: #11096 (batching of the same seeder, closed), #4967 (a refused capability declaration suppressing the back-compat derivation, closed). No open card covers this.
⛔ Ungraded — triage sets type and priority. Filed unassigned.
Generated by Claude Code