Skip to content

feat(extensions): least-privilege capability reconciliation (pm-fk84) - #326

Merged
unbraind merged 2 commits into
mainfrom
feat-least-privilege-capability-reconciliation
Jun 21, 2026
Merged

unbraind merged 2 commits into
mainfrom
feat-least-privilege-capability-reconciliation

Conversation

@unbraind

@unbraind unbraind commented Jun 21, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes the least-privilege loop for the extension/package platform. The loader already blocks an extension that registers a surface whose capability is undeclared (extension_capability_missing). This PR adds the inverse, advisory direction: detect capabilities a manifest declares but never exercises at activation, so package authors can trim manifests to the minimum grant.

Tracks pm-fk84 under epic pm-ugqx (extension & package platform maturity). Complements pm-mwwp (missing-capability enforcement) and pm-bav0 (scaffold least-privilege metadata).

What changed

Core — src/core/extensions/capability-usage.ts (new)

  • EXTENSION_CAPABILITY_REGISTRATION_SURFACES: canonical capability→surface map. satisfies Record<ExtensionCapability, …> makes it exhaustive at compile time — adding a capability without a registration surface fails the build, which prevents a capability with no surface from being perpetually flagged "unused".
  • collectUsedExtensionCapabilities(activation, { extensionName? }) and reconcileExtensionCapabilityUsage(loaded, activation): pure functions over the activation result. Declared capabilities are filtered to known ones (unknowns remain the separate extension_capability_unknown diagnostic); entries are keyed by a JSON [layer, name] tuple so same-named extensions on different layers stay distinct.

Doctor — extension/doctor.ts + extension.ts

  • classifyUnusedCapabilityWarnings emits advisory extension_capability_unused:<layer>:<name>:<capability> plus a least-privilege remediation hint in the triage summary.
  • Advisory only: the doctor's top-level ok stays true (status: "warn"). It is deliberately not wired into pm health — isAdvisoryHealthWarning only whitelists telemetry_*/history_*, so any other extension warning would block the health gate and the daily auto-release. Verified pm health is unaffected.

SDK — sdk/testing.ts + sdk/index.ts + core/extensions/index.ts

  • assertExtensionCapabilityUsage(activation, { declared, extensionName?, allowUnused? }): the package-test counterpart of the advisory warning. Pass the same capabilities as manifest.capabilities; it throws on over-declaration. Rejects unknown declared capabilities. Barrel-re-exported with a barrel-equality lock.
  • Exported collectUsedExtensionCapabilities, reconcileExtensionCapabilityUsage, and EXTENSION_CAPABILITY_REGISTRATION_SURFACES from the SDK barrel.

Docs — EXTENSIONS.md (advisory warning + workflow) and SDK.md (helper + exports).

Verification

  • 4807 tests pass, 100/100/100/100 coverage (statements/branches/functions/lines).
  • typecheck, quality:static (docstring coverage), quality:docs-skills, changelog:pm:check (no drift), and security:scan all green.
  • The repo's own 10 first-party packages + builtins are already least-privilege → 0 new warnings.
  • Sandbox dogfood: a package declaring ["commands","schema","search"] that only registers a command is flagged for schema+search; trimming to ["commands"] clears it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CHP2yQxWtXG8ZfYvRSoW33

Summary by Sourcery

Introduce least-privilege capability reconciliation for extensions by comparing declared capabilities with those actually exercised at activation, surfacing over-declared permissions via doctor warnings, SDK helpers, and documentation updates.

New Features:

  • Add core capability-usage reconciliation module that maps extension capabilities to registration surfaces and computes per-extension declared/used/unused capabilities.
  • Expose least-privilege capability reconciliation helpers and types through the core extensions and public SDK barrels.
  • Introduce an SDK testing helper to assert that extension manifests declare no unused capabilities, with support for allow-listed exceptions.

Enhancements:

  • Extend pm package doctor to emit advisory extension_capability_unused warnings with least-privilege remediation guidance while keeping health checks non-blocking.
  • Tighten capability metadata guarantees by enforcing that every known capability has at least one mapped registration surface and maintaining distinct usage across layers.

Documentation:

  • Document least-privilege capability workflows and the new SDK assertion and reconciliation helpers in EXTENSIONS.md and SDK.md.

Detect capabilities an extension manifest declares but never exercises at
activation — the inverse of the existing missing-capability enforcement — so
package authors can trim manifests to least privilege.

Core (src/core/extensions/capability-usage.ts):
- EXTENSION_CAPABILITY_REGISTRATION_SURFACES: canonical capability->surface map,
  `satisfies Record<ExtensionCapability,...>` makes it exhaustive at compile time
  so a new capability without a surface fails the build (no perpetual false
  "unused" positive).
- collectUsedExtensionCapabilities / reconcileExtensionCapabilityUsage: pure
  functions over the activation result; declared is filtered to KNOWN
  capabilities (unknowns stay the separate diagnostic); keyed by a JSON
  [layer,name] tuple to keep same-named extensions on different layers distinct.

Doctor (extension/doctor.ts + extension.ts):
- classifyUnusedCapabilityWarnings emits advisory
  extension_capability_unused:<layer>:<name>:<capability> plus a least-privilege
  remediation hint. Advisory only — doctor `ok` stays true and it is
  deliberately NOT wired into pm health (any non-telemetry/history extension
  warning would block the health gate and the daily release).

SDK (sdk/testing.ts + sdk/index.ts + core/extensions/index.ts):
- assertExtensionCapabilityUsage(activation, { declared, extensionName?,
  allowUnused? }) — package-test counterpart that fails on over-declaration.
- Exported the reconciliation helpers + surfaces map from the SDK barrel.

Docs: EXTENSIONS.md (advisory warning) and SDK.md (helper + exports).

100% coverage (4807 tests); typecheck, quality:static (docstrings),
docs-skills, changelog, and secret-scan all green. Repo's own 10 first-party
packages + builtins are already least-privilege (0 warnings).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHP2yQxWtXG8ZfYvRSoW33
@sourcery-ai

sourcery-ai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Implements least-privilege capability reconciliation for extensions by tracking which capabilities are actually exercised at activation, surfacing declared-but-unused capabilities as advisory doctor warnings and as a failing SDK test helper, while exposing the core reconciliation logic and updating docs and changelog.

Sequence diagram for extension doctor least-privilege capability warnings

sequenceDiagram
  actor PackageAuthor
  participant ExtensionDoctor as extension_doctor_command
  participant Loader as loadExtensions
  participant Activator as activateExtensions
  participant UnusedClassifier as classifyUnusedCapabilityWarnings
  participant Reconciler as reconcileExtensionCapabilityUsage

  PackageAuthor->>ExtensionDoctor: runExtension --doctor
  ExtensionDoctor->>Loader: loadExtensions
  Loader-->>ExtensionDoctor: loadResult
  ExtensionDoctor->>Activator: activateExtensions
  Activator-->>ExtensionDoctor: activationResult

  ExtensionDoctor->>UnusedClassifier: classifyUnusedCapabilityWarnings(loadResult, activationResult)
  UnusedClassifier->>Reconciler: reconcileExtensionCapabilityUsage(loadResult.loaded, activationResult)
  Reconciler-->>UnusedClassifier: ExtensionCapabilityUsageReconciliation[]
  UnusedClassifier-->>ExtensionDoctor: ["extension_capability_unused:…"]

  ExtensionDoctor->>ExtensionDoctor: buildExtensionTriageSummary(warnings)
  ExtensionDoctor-->>PackageAuthor: advisory extension_capability_unused warnings
Loading

Sequence diagram for SDK assertExtensionCapabilityUsage helper

sequenceDiagram
  actor PackageTest as Package_test
  participant Assertion as assertExtensionCapabilityUsage
  participant CapabilityCheck as isKnownExtensionCapability
  participant UsageCollector as collectUsedExtensionCapabilities

  PackageTest->>Assertion: assertExtensionCapabilityUsage(activation, expectation)
  loop for each expectation.declared
    Assertion->>CapabilityCheck: isKnownExtensionCapability(normalized)
    CapabilityCheck-->>Assertion: boolean
    Assertion->>Assertion: add normalized to declaredSet
  end

  Assertion->>UsageCollector: collectUsedExtensionCapabilities(activation, { extensionName })
  UsageCollector-->>Assertion: used capabilities

  Assertion->>Assertion: compute unused = declared - used - allowUnused
  alt unused is not empty
    Assertion-->>PackageTest: throw Error
  else unused is empty
    Assertion-->>PackageTest: return { declared, used, unused }
  end
Loading

File-Level Changes

Change Details Files
Add core capability-usage reconciliation module and tests to map declared vs. exercised extension capabilities
  • Introduce EXTENSION_CAPABILITY_REGISTRATION_SURFACES to exhaustively map each known capability to its registration surfaces with a compile-time guard
  • Implement attributeCapabilityUsage and collectUsedExtensionCapabilities to infer used capabilities from real activation data, with optional per-extension filtering
  • Implement reconcileExtensionCapabilityUsage to compare normalized declared capabilities with used ones per (layer,name) and report unused grants while ignoring unknown capabilities
  • Add unit tests covering mapping completeness, usage attribution, multi-extension aggregation, per-layer separation, and edge cases like inert or unknown-capability manifests
src/core/extensions/capability-usage.ts
tests/unit/core/extensions/capability-usage.spec.ts
Expose capability-usage helpers and add SDK-level least-privilege assertion for package tests
  • Add ExtensionCapabilityUsageExpectation and ExtensionCapabilityUsageAssertion interfaces to describe declared/used/unused capability expectations
  • Implement assertExtensionCapabilityUsage in the SDK to normalize/validate declared capabilities, compute used capabilities via collectUsedExtensionCapabilities, enforce least-privilege with optional allowUnused, and return the breakdown
  • Wire SDK tests to ensure assertExtensionCapabilityUsage is exported from the public barrel and behaves correctly across success, normalization, allowUnused, and error cases
  • Re-export assertExtensionCapabilityUsage and related capability-usage helpers/types from sdk/index.ts and core/extensions/index.ts
src/sdk/testing.ts
src/sdk/index.ts
src/core/extensions/index.ts
tests/unit/sdk/sdk-index.spec.ts
Integrate unused-capability detection into extension doctor flow as advisory warnings
  • Add classifyUnusedCapabilityWarnings to compute extension_capability_unused::: tokens from reconciliation results
  • Invoke classifyUnusedCapabilityWarnings in the extension doctor command so unused capability warnings participate in doctor output without affecting ok status
  • Extend buildExtensionTriageSummary to add a least-privilege remediation message whenever extension_capability_unused warnings are present
  • Add unit coverage to verify warning tokens, triage status/remediation, and that least-privilege manifests yield no warnings
src/cli/commands/extension/doctor.ts
src/cli/commands/extension.ts
tests/unit/extensions/extension-command.spec.ts
Document and announce least-privilege capability reconciliation in docs and changelog
  • Update SDK documentation to describe assertExtensionCapabilityUsage and the exported capability-usage helpers
  • Update extensions documentation to explain extension_capability_unused vs. extension_capability_missing and how to use the SDK helper to catch over-declaration
  • Add an Unreleased changelog entry referencing the new least-privilege functionality and feature id
docs/SDK.md
docs/EXTENSIONS.md
CHANGELOG.md
Track feature implementation metadata in pm agent files
  • Add feature description file for pm-fk84
  • Add corresponding history entry for pm-fk84
.agents/pm/features/pm-fk84.toon
.agents/pm/history/pm-fk84.jsonl

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@unbraind

Copy link
Copy Markdown
Owner Author

@codex review

@unbraind

Copy link
Copy Markdown
Owner Author

/gemini review

@coderabbitai

coderabbitai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@unbraind, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 38 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 3a39242c-b669-4a50-aa48-852598e807f8

📥 Commits

Reviewing files that changed from the base of the PR and between e436c4a and 58fadb2.

📒 Files selected for processing (16)
  • .agents/pm/features/pm-fk84.toon
  • .agents/pm/history/pm-fk84.jsonl
  • CHANGELOG.md
  • docs/EXTENSIONS.md
  • docs/SDK.md
  • src/cli/commands/extension.ts
  • src/cli/commands/extension/doctor.ts
  • src/core/extensions/capability-usage.ts
  • src/core/extensions/extension-capability-aliases.ts
  • src/core/extensions/index.ts
  • src/sdk/index.ts
  • src/sdk/testing.ts
  • tests/unit/core/extensions/capability-usage.spec.ts
  • tests/unit/extensions/extension-command.spec.ts
  • tests/unit/extensions/runtime-registrations.spec.ts
  • tests/unit/sdk/sdk-index.spec.ts
📝 Walkthrough

Walkthrough

Adds least-privilege capability reconciliation to the pm extension system. A new core module cross-checks extension manifest-declared capabilities against those actually exercised at activation. The doctor CLI command now emits extension_capability_unused advisory warnings for over-declared capabilities, and a new SDK assertExtensionCapabilityUsage test helper lets extension authors catch over-declaration in tests.

Changes

Least-privilege extension capability reconciliation

Layer / File(s) Summary
Core capability-usage reconciliation module
src/core/extensions/capability-usage.ts, src/core/extensions/index.ts
New module exports EXTENSION_CAPABILITY_REGISTRATION_SURFACES (compile-time exhaustive mapping), internal normalization/keying helpers, activation attribution across all registration substructures, collectUsedExtensionCapabilities, reconcileExtensionCapabilityUsage, and associated types. index.ts re-exports all new symbols.
Doctor triage: classify and surface unused-capability warnings
src/cli/commands/extension/doctor.ts, src/cli/commands/extension.ts
doctor.ts adds classifyUnusedCapabilityWarnings emitting extension_capability_unused:<layer>:<name>:<capability> tokens and extends buildExtensionTriageSummary with least-privilege remediation guidance. extension.ts wires the new classifier into the doctor action's warning aggregation pipeline.
SDK testing helper and public barrel exports
src/sdk/testing.ts, src/sdk/index.ts
testing.ts adds ExtensionCapabilityUsageExpectation/ExtensionCapabilityUsageAssertion interfaces and assertExtensionCapabilityUsage, which validates declared capabilities, computes used set, applies allowUnused suppression, and throws on remaining unused. sdk/index.ts re-exports these plus all reconciliation utilities.
Unit tests
tests/unit/core/extensions/capability-usage.spec.ts, tests/unit/extensions/extension-command.spec.ts, tests/unit/sdk/sdk-index.spec.ts
Tests cover the full reconciliation matrix (all surfaces, cross-layer, filtering, normalization, empty activation), classifyUnusedCapabilityWarnings and triage output, and assertExtensionCapabilityUsage behavior including error messages, allowUnused, and unknown capability rejection.
Docs, changelog, and agent records
docs/EXTENSIONS.md, docs/SDK.md, CHANGELOG.md, .agents/pm/...
EXTENSIONS.md documents extension_capability_unused/extension_capability_missing behaviors. SDK.md adds entries for the new testing helper and reconciliation exports. CHANGELOG.md gets an Unreleased entry. Agent feature/history records for pm-fk84 are added.

Sequence Diagram(s)

sequenceDiagram
  participant Author as Extension Author
  participant DoctorAction as pm package doctor
  participant Classifier as classifyUnusedCapabilityWarnings
  participant Reconciler as reconcileExtensionCapabilityUsage
  participant Triage as buildExtensionTriageSummary

  rect rgba(70, 130, 180, 0.5)
    note over DoctorAction,Reconciler: Doctor CLI flow
    DoctorAction->>Classifier: classifyUnusedCapabilityWarnings(loadResult, activationResult)
    Classifier->>Reconciler: reconcileExtensionCapabilityUsage(loaded, activation)
    Reconciler-->>Classifier: [{declared, used, unused}] per extension
    Classifier-->>DoctorAction: extension_capability_unused:layer:name:cap tokens
    DoctorAction->>Triage: warnings[]
    Triage-->>DoctorAction: warn status + least-privilege remediation text
  end

  rect rgba(60, 179, 113, 0.5)
    note over Author,Reconciler: SDK test-time assertion
    Author->>assertExtensionCapabilityUsage: activation, {declared, extensionName, allowUnused}
    assertExtensionCapabilityUsage->>Reconciler: collectUsedExtensionCapabilities(activation, filter)
    Reconciler-->>assertExtensionCapabilityUsage: used[]
    assertExtensionCapabilityUsage-->>Author: {declared, used, unused} or throws on over-declaration
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • unbraind/pm-cli#123: Updates pm-governance-audit to declare and register the hooks capability — directly exercised by this PR's reconcileExtensionCapabilityUsage logic which cross-checks declared vs. exercised capabilities and would emit extension_capability_unused if such declarations were not matched.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementing least-privilege capability reconciliation for extensions, directly matching the changeset's core purpose.
Description check ✅ Passed The description is directly related to the changeset, providing detailed explanations of changes across core, doctor, SDK, and documentation components with verification details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces least-privilege capability reconciliation for extensions, detecting capabilities declared in manifests but never exercised during activation. It adds advisory doctor warnings (extension_capability_unused) and an SDK testing helper (assertExtensionCapabilityUsage) to catch over-declaration. Feedback on the changes suggests optimizing the lookup key serialization in capability-usage.ts by replacing JSON serialization with a simpler delimiter-based string to reduce performance overhead, and validating the allowUnused capabilities in the SDK helper to prevent silent typos.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +72 to +78
function usageKey(layer: ExtensionLayer, name: string): string {
return JSON.stringify([layer, normalizeExtensionName(name)]);
}

function usageKeyName(key: string): string {
return (JSON.parse(key) as [ExtensionLayer, string])[1];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using JSON.stringify and JSON.parse to serialize and deserialize the lookup key introduces unnecessary performance overhead. Since layer is strictly typed as ExtensionLayer ("global" | "project"), it can never contain a delimiter like |. We can use a simple delimiter-based string serialization which is 100% collision-proof, cleaner, and significantly faster.

Suggested change
function usageKey(layer: ExtensionLayer, name: string): string {
return JSON.stringify([layer, normalizeExtensionName(name)]);
}
function usageKeyName(key: string): string {
return (JSON.parse(key) as [ExtensionLayer, string])[1];
}
function usageKey(layer: ExtensionLayer, name: string): string {
return layer + "|" + normalizeExtensionName(name);
}
function usageKeyName(key: string): string {
return key.slice(key.indexOf("|") + 1);
}

Comment thread src/sdk/testing.ts Outdated
const declared = [...declaredSet].sort((left, right) => left.localeCompare(right));
const used = collectUsedExtensionCapabilities(activation, { extensionName: expectation.extensionName });
const usedSet = new Set(used);
const allowUnused = new Set((expectation.allowUnused ?? []).map((capability) => capability.trim().toLowerCase()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While expectation.declared is validated to ensure all entries are known extension capabilities, expectation.allowUnused is not validated. If a user makes a typo in allowUnused (e.g., "schem" instead of "schema"), it will be silently ignored, potentially leading to confusing test failures. We should validate expectation.allowUnused in the same way.

Suggested change
const allowUnused = new Set((expectation.allowUnused ?? []).map((capability) => capability.trim().toLowerCase()));
const allowUnused = new Set<ExtensionCapability>();
if (expectation.allowUnused) {
for (const capability of expectation.allowUnused) {
const normalized = capability.trim().toLowerCase();
if (!isKnownExtensionCapability(normalized)) {
throw new Error(
"Expected allowed unused capability \"" + capability + "\" to be a known extension capability. " +
"Use canonical capability names (see manifest.capabilities)."
);
}
allowUnused.add(normalized);
}
}

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The capability normalization and known-capability filtering logic is duplicated between normalizeDeclaredCapabilities in capability-usage.ts and assertExtensionCapabilityUsage; consider centralizing this in a shared helper to keep behavior and error messaging consistent over time.
  • In assertExtensionCapabilityUsage, entries in allowUnused are normalized but not validated via isKnownExtensionCapability, which means a typo can silently bypass the least-privilege guard; it may be safer to apply the same known-capability validation used for declared.
  • The reconciliation logic currently keys usage by layer + normalized name but only exposes the original name in ExtensionCapabilityUsageReconciliation; if names can differ by case or whitespace between manifest and activation, consider returning both original and normalized names or enforcing a single canonical form earlier to avoid subtle mismatches.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The capability normalization and known-capability filtering logic is duplicated between `normalizeDeclaredCapabilities` in `capability-usage.ts` and `assertExtensionCapabilityUsage`; consider centralizing this in a shared helper to keep behavior and error messaging consistent over time.
- In `assertExtensionCapabilityUsage`, entries in `allowUnused` are normalized but not validated via `isKnownExtensionCapability`, which means a typo can silently bypass the least-privilege guard; it may be safer to apply the same known-capability validation used for `declared`.
- The reconciliation logic currently keys usage by `layer` + normalized `name` but only exposes the original `name` in `ExtensionCapabilityUsageReconciliation`; if names can differ by case or whitespace between manifest and activation, consider returning both original and normalized names or enforcing a single canonical form earlier to avoid subtle mismatches.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements least-privilege capability reconciliation for extensions, flagging declared-but-unused capabilities as advisory warnings in the package doctor and introducing the assertExtensionCapabilityUsage SDK helper for testing. The core logic is added in a new capability-usage.ts module, with corresponding CLI integration, documentation updates, and unit tests. Feedback on the implementation suggests optimizing the lookup key serialization in capability-usage.ts by replacing JSON.stringify and JSON.parse with a delimited string to reduce CPU and memory overhead in hot paths.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +72 to +78
function usageKey(layer: ExtensionLayer, name: string): string {
return JSON.stringify([layer, normalizeExtensionName(name)]);
}

function usageKeyName(key: string): string {
return (JSON.parse(key) as [ExtensionLayer, string])[1];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using JSON.stringify and JSON.parse to serialize and deserialize the lookup key in a hot path (such as attributeCapabilityUsage and collectUsedExtensionCapabilities which iterate over all registrations) introduces unnecessary CPU and memory overhead.

Since layer is strictly "global" | "project", we can use a safe delimiter like \u0000 (NUL character escape sequence) to separate the layer and the normalized name. This avoids embedding a literal NUL byte in the source file (preserving grep compatibility) while being significantly faster than JSON serialization.

Suggested change
function usageKey(layer: ExtensionLayer, name: string): string {
return JSON.stringify([layer, normalizeExtensionName(name)]);
}
function usageKeyName(key: string): string {
return (JSON.parse(key) as [ExtensionLayer, string])[1];
}
function usageKey(layer: ExtensionLayer, name: string): string {
return `${layer}\\u0000${normalizeExtensionName(name)}`;
}
function usageKeyName(key: string): string {
return key.split("\\u0000")[1];
}

…conciliation

- Replace the JSON-tuple usage key with `${layer}:${name}`. `layer` is a closed
  enum that never contains a colon, so the first colon always separates layer
  from the (possibly colon-bearing) name — collision-proof without
  serialization overhead (Gemini).
- Validate `allowUnused` entries in assertExtensionCapabilityUsage so a typo
  cannot silently bypass the least-privilege guard (Gemini + Sourcery).
- Extract the shared `normalizeKnownExtensionCapability` token helper into
  extension-capability-aliases.js and use it from both the core reconciliation
  and the SDK assertion, removing duplicated trim/lower/known-filter logic
  (Sourcery).

100% coverage maintained (4807 tests); typecheck + quality:static green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHP2yQxWtXG8ZfYvRSoW33
@unbraind

Copy link
Copy Markdown
Owner Author

Thanks for the reviews. Addressed in 58fadb2:

  • Gemini (capability-usage.ts key): replaced the JSON.stringify/parse lookup key with \${layer}:\${name}. layer is a closed enum (global/project) that never contains a colon, so the first colon always separates the layer from the (possibly colon-bearing) name — collision-proof with no serialization overhead.
  • Gemini + Sourcery (allowUnused not validated): assertExtensionCapabilityUsage now validates allowUnused entries against the known-capability set too, so a typo (e.g. "schem") throws instead of silently bypassing the guard.
  • Sourcery (duplicated normalization): extracted a shared normalizeKnownExtensionCapability token helper into extension-capability-aliases.ts, used by both the core reconciliation and the SDK assertion.
  • Sourcery (name canonicalization): in the doctor path the loaded names and the activation registration names both originate from the same extension.name, so matching is exact; the normalized key only hardens the user-supplied extensionName filter. The reconciliation output intentionally preserves the original manifest name.

100% coverage maintained (4807 tests); typecheck + quality:static green.

@coderabbitai full review
@codex review
/gemini review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

@unbraind Thanks for the detailed summary of changes in 58fadb2 — all four fixups look well-reasoned:

  • The ${layer}:${name} key is clean and collision-proof given the closed enum constraint.
  • Validating allowUnused entries against the known-capability set closes a real silent-failure gap.
  • Centralising the normalization helper in extension-capability-aliases.ts is the right call for long-term maintainability.
  • The explanation around name canonicalization (exact match in the doctor path; normalization only for the user-supplied filter) is clear.

Triggering a fresh full review now.

✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements least-privilege capability reconciliation for extension packages. It introduces core logic to detect declared-but-unused capabilities at activation, surfaces these as advisory warnings in the package doctor tool, and provides a new SDK test helper assertExtensionCapabilityUsage for package authors to catch over-declaration during testing. The changes are well-documented, thoroughly tested, and integrate cleanly with the existing codebase. No review comments were provided, and I have no additional feedback.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@unbraind
unbraind merged commit f82310f into main Jun 21, 2026
16 checks passed
@unbraind
unbraind deleted the feat-least-privilege-capability-reconciliation branch June 21, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant