Skip to content

feat(policy): support native PolicyPlugin exports from module files#114

Closed
yxbh wants to merge 3 commits into
microsoft:mainfrom
yxbh:feat/native-plugin-loader
Closed

feat(policy): support native PolicyPlugin exports from module files#114
yxbh wants to merge 3 commits into
microsoft:mainfrom
yxbh:feat/native-plugin-loader

Conversation

@yxbh

@yxbh yxbh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • What changed
  • Why it changed

What changed

Add support for loading native PolicyPlugin exports directly from .js/.mjs module files. The plugin loader (loadPluginChain) now detects modules that export a PolicyPlugin object (identified by a meta property) and adds them directly to the plugin chain with trusted-code trust, instead of requiring compilation through PolicyConfig.

Files changed:

File Change
packages/core/.../policy/types.ts Add isNativePlugin() type guard and validateNativePlugin()
packages/core/.../policy/index.ts Export new functions
packages/core/.../policy.ts Update loadPolicy() return type to PolicyConfig | PolicyPlugin, detect native exports, fix Windows import() with pathToFileURL
packages/core/.../policy/loader.ts Handle native plugins in loadPluginChain() — add directly to chain
packages/core/.../readiness/index.ts Guard legacy resolveChain path against native plugin exports
src/.../__tests__/policy-engine-types.test.ts 14 tests for isNativePlugin and validateNativePlugin
src/.../__tests__/policy-loader.test.ts 6 tests for native plugin loading and engine execution
src/.../__tests__/policy.test.ts Fix type narrowing for loadPolicy() union return type

Why it changed

The plugin engine already supports a rich 5-stage pipeline (detect → afterDetect → beforeRecommend → recommend → afterRecommend) with immutable patch types (SignalPatch, RecommendationPatch). However, loadPluginChain() only accepted PolicyConfig objects and compiled them — module authors could not export a raw PolicyPlugin to use the full hook API.

This limited what policy modules can do:

  • No signal mutation — can't modify signals from other detectors (e.g., change status, add metadata)
  • No cross-signal recommendations — can't compose recommendations based on signals from multiple detectors
  • No recommendation patching — can't modify or remove recommendations from other plugins

Corporate/enterprise policies (e.g., tool governance, MCP server allow/deny lists) need these capabilities.

Example usage

// Before: PolicyConfig (criteria DSL) — limited to add/disable/override
export default {
  name: 'my-policy',
  criteria: { add: [...], disable: [...] }
};

// After: Native PolicyPlugin — full lifecycle hooks
export default {
  meta: { name: 'my-policy', sourceType: 'module', trust: 'trusted-code' },
  afterDetect: async (signals, ctx) => ({
    modify: signals
      .filter(s => s.metadata?.type === 'cursorrules')
      .map(s => ({ id: s.id, changes: { status: 'error', metadata: { blocked: true } } }))
  }),
  afterRecommend: async (recs, signals, ctx) => ({
    add: [{
      id: 'custom-rec', signalId: '...', impact: 'critical',
      message: '...', origin: { addedBy: 'my-policy' }
    }]
  })
};

Checklist

  • Tests added or updated (20 new tests, 657 total passing)
  • Lint/typecheck pass
  • Docs updated if needed (no user-facing docs changes needed — this extends an internal API)

Copilot AI review requested due to automatic review settings April 7, 2026 02:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class support for module-exported “native” PolicyPlugin objects (with lifecycle hooks) alongside existing PolicyConfig-based policies, so policy authors can use the full plugin engine pipeline.

Changes:

  • Introduces isNativePlugin() and validateNativePlugin() to detect/validate native plugin exports.
  • Updates loadPolicy() and loadPluginChain() to load native PolicyPlugin exports and force sourceType: "module" + trust: "trusted-code".
  • Adds tests covering type guards/validation, loading native plugins, and engine execution behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/services/tests/policy-loader.test.ts Adds end-to-end tests for loading/executing native plugin modules in a plugin chain.
src/services/tests/policy-engine-types.test.ts Adds unit tests for isNativePlugin() and validateNativePlugin().
packages/core/src/services/readiness/index.ts Guards the legacy resolveChain path against native plugin exports.
packages/core/src/services/policy/types.ts Adds the native plugin type guard + validator helpers.
packages/core/src/services/policy/loader.ts Accepts native plugin exports directly into the engine chain with trusted-code trust.
packages/core/src/services/policy/index.ts Re-exports the new helper APIs from the policy service entrypoint.
packages/core/src/services/policy.ts Expands loadPolicy() to return `PolicyConfig

Comment thread packages/core/src/services/policy.ts
Comment thread packages/core/src/services/policy/types.ts Outdated
Comment thread packages/core/src/services/readiness/index.ts Outdated
Comment thread src/services/__tests__/policy-loader.test.ts Outdated
Comment thread src/services/__tests__/policy-loader.test.ts
Add support for loading native PolicyPlugin exports directly from .js/.mjs
module files, enabling policy authors to use the full plugin lifecycle API
(afterDetect, beforeRecommend, afterRecommend hooks with SignalPatch and
RecommendationPatch) instead of being limited to the criteria.add/disable/
override DSL.

Changes:
- Add isNativePlugin() type guard and validateNativePlugin() to types.ts
- Update loadPolicy() to detect and return native plugin exports
- Update loadPluginChain() to add native plugins directly to the chain
  with trusted-code trust and module sourceType
- Guard legacy resolveChain path against native plugin exports
- Fix type narrowing in existing policy.test.ts for union return type
- Add 20 new tests covering type guard, validation, loading, and
  end-to-end engine execution of native plugins

Native plugins are detected by the presence of a meta property (with
meta.name) on the module export, distinguishing them from PolicyConfig
objects which have a top-level name string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@yxbh yxbh force-pushed the feat/native-plugin-loader branch from c37df43 to 4c27ac7 Compare April 7, 2026 03:04
- Tighten isNativePlugin() guard to require absence of root-level name
  string, preventing misclassification of PolicyConfig exports that
  happen to include a meta object
- Fix Windows import() bug: use pathToFileURL() for local module paths
  to avoid ERR_UNSUPPORTED_ESM_URL_SCHEME on drive-letter paths (C:\)
- Reword legacy path error message to be user-actionable instead of
  saying 'internal error'
- Remove unused pathToFileURL import from test file

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 7, 2026 03:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread packages/core/src/services/policy/types.ts
Comment thread packages/core/src/services/policy/types.ts
Comment thread packages/core/src/services/policy.ts
Copilot AI review requested due to automatic review settings April 8, 2026 01:19
@yxbh yxbh force-pushed the feat/native-plugin-loader branch from 16f5a47 to f381220 Compare April 8, 2026 01:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Comment thread packages/core/src/services/policy/types.ts
Comment thread packages/core/src/services/policy.ts
Comment thread packages/core/src/services/readiness/index.ts Outdated
Comment thread examples/policies/ms-corporate-native.mjs Outdated
@yxbh yxbh force-pushed the feat/native-plugin-loader branch from f381220 to bcea5be Compare April 8, 2026 01:24
Copilot AI review requested due to automatic review settings April 8, 2026 01:28
@yxbh yxbh force-pushed the feat/native-plugin-loader branch from bcea5be to bd4dc8c Compare April 8, 2026 01:28
@yxbh yxbh force-pushed the feat/native-plugin-loader branch from bd4dc8c to 76e8fdf Compare April 8, 2026 01:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread packages/core/src/services/policy/types.ts
Comment thread packages/core/src/services/policy.ts
Comment thread packages/core/src/services/readiness/index.ts
Address Copilot review comments:

1. isNativePlugin()  improved doc comment to describe actual detection
   rules (meta.name required, root-level name rejected). Added validation
   of meta.sourceType and meta.trust when present.

2. validateNativePlugin()  now validates that hooks are functions, that
   detectors/recommenders are arrays, and that each array element has a
   valid id (non-empty string) and callable detect/recommend function.
   Errors are caught at load time with clear messages.

3. loadPolicy() doc comment  aligned with actual isNativePlugin() rules.

4. readiness/index.ts  auto-enable engine path for native plugins
   instead of throwing in legacy criteria path.

Added 12 new tests (669 total passing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@yxbh yxbh force-pushed the feat/native-plugin-loader branch from 76e8fdf to 2552aef Compare April 8, 2026 01:39
@yxbh

yxbh commented Apr 8, 2026

Copy link
Copy Markdown
Contributor Author

Closing to open a clean PR with squashed history.

@yxbh yxbh closed this Apr 8, 2026
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.

2 participants