{"slug": "plugin-sdk-core-v1-and-secrets-local-provider-extraction", "title": "Plugin SDK Core V1 and Secrets Local Provider Extraction", "summary": "Signet announced Plugin SDK Core V1 and Secrets Local Provider Extraction, introducing a daemon-owned plugin host for bundled TypeScript core plugins with a manifest and registry model. The release extracts local encrypted secrets behind a provider interface while preserving existing user data and API behavior, laying groundwork for future marketplace support without implementing it in V1.", "body_md": "# Plugin SDK Core V1 and Secrets Local Provider Extraction\n\n## Problem\n\nThe broader Plugin SDK planning epic defines the destination: Signet plugins as cross-surface capability modules that can extend daemon, CLI, MCP, dashboard, SDK, connectors, and prompt lifecycle surfaces.\n\nThat full destination is intentionally larger than a first implementation. If we try to implement TypeScript plugins, Rust sidecars, marketplace install, dynamic UI mounting, prompt composition, and every secret provider at once, the PR becomes an everything-bagel and the trust boundary gets blurry.\n\nV1 should prove the architecture with the smallest useful slice:\n\n- a daemon-owned plugin host for bundled TypeScript core plugins,\n- a manifest and registry model that will survive marketplace support later,\n- prompt contribution plumbing with visibility and disable behavior,\n- surface metadata plumbing for CLI/MCP/dashboard/connectors without requiring dynamic loading everywhere yet,\n`signet.secrets`\n\nrepresented as a privileged core plugin,- the current local encrypted secrets implementation extracted behind a local provider interface without changing existing user data.\n\n## Goals\n\n- Add a plugin host skeleton owned by the daemon.\n- Support bundled TypeScript core plugin manifests.\n- Persist plugin registry and lifecycle state.\n- Expose plugin status and diagnostics through daemon API.\n- Support append/context prompt contributions with provenance, token budgets, ordering, and disable behavior.\n- Add a surface metadata registry for daemon, CLI, MCP, dashboard, SDK, and connector contributions.\n- Represent Signet Secrets as the first privileged bundled core plugin.\n- Extract local secret storage behind a provider interface while preserving\n`secrets.enc`\n\nbyte-for-byte unless a user writes a new/updated secret. - Keep existing\n`/api/secrets/*`\n\n, CLI, MCP, dashboard, and SDK behavior working. - Store marketplace-ready manifest metadata without implementing marketplace install.\n\n## Non-Goals\n\n- No marketplace install, review, ranking, payments, or public discovery.\n- No third-party plugin execution.\n- No Rust sidecar execution in V1.\n- No WASI runtime.\n- No native dynamic-library plugin loading.\n- No dynamic dashboard panel rendering from arbitrary plugin code.\n- No dynamic CLI command loading from arbitrary plugin code.\n- No Bitwarden, Vault, AWS, GCP, Azure, pass/gopass, or env provider implementation.\n- No secret store format migration.\n- No raw secret read endpoint.\n- No plugin-authored mutation of user prompts beyond append/context contributions.\n- No removal of legacy secrets compatibility routes.\n\n## Architecture\n\n```\nDaemon\n+-- plugin host\n|   +-- manifest validator\n|   +-- registry store\n|   +-- lifecycle state\n|   +-- capability grants\n|   +-- surface metadata registry\n|   +-- prompt contribution registry\n|   +-- health/status diagnostics\n|\n+-- bundled core plugins\n|   +-- signet.secrets\n|\n+-- existing API/CLI/MCP/dashboard/connectors\n    +-- continue calling existing compatibility surfaces\n    +-- can read plugin status/metadata where useful\n```\n\nV1 does not make every Signet surface dynamically plugin-rendered. Instead, it creates the host and metadata contract those surfaces will later consume. Existing first-party surfaces remain hand-wired where necessary, but they are associated with the plugin that owns them.\n\n## Plugin Manifest Contract\n\nV1 manifests are data contracts, not arbitrary execution permissions.\n\nRequired fields:\n\n```\ninterface PluginManifestV1 {\n  readonly id: string;\n  readonly name: string;\n  readonly version: string;\n  readonly publisher: string;\n  readonly description: string;\n  readonly runtime: PluginRuntimeV1;\n  readonly compatibility: PluginCompatibilityV1;\n  readonly trustTier: PluginTrustTier;\n  readonly capabilities: readonly string[];\n  readonly surfaces: PluginSurfaceDeclarationsV1;\n  readonly marketplace?: PluginMarketplaceMetadataV1;\n  readonly docs: PluginDocsMetadataV1;\n}\n```\n\nRuntime in V1:\n\n```\ninterface PluginRuntimeV1 {\n  readonly language: \"typescript\" | \"rust\";\n  readonly kind: \"bundled-module\" | \"sidecar\" | \"wasi\" | \"host-managed\";\n  readonly entry?: string;\n  readonly protocol?: string;\n}\n```\n\nV1 only executes:\n\n```\nlanguage=typescript\nkind=bundled-module\ntrustTier=core\n```\n\nV1 can also activate `host-managed`\n\nverified/core plugin metadata when the\nimplementation is native Signet code and no external plugin runtime is executed.\nRust, sidecar, and WASI manifest fields are accepted for forward-compatible\nmetadata and status reporting, but those plugins enter `blocked`\n\nwith an\nunsupported-runtime reason until later specs implement execution.\n\nValidation rules:\n\n`id`\n\nis stable and globally unique.`version`\n\nis SemVer.`publisher`\n\nis required.`compatibility.signet`\n\nand`compatibility.pluginApi`\n\nare required.- Every declared surface must map to at least one declared capability.\n- Every declared capability must have docs metadata.\n- Only Signet-owned bundled metadata can mark a plugin as\n`trustTier=core`\n\n. - Unsupported runtimes are recorded but not started.\n\n## Registry and Persistence Contract\n\nThe daemon persists plugin state. The implementation may use SQLite or a JSON file in V1, but it must expose the same logical fields.\n\nLogical record:\n\n```\ninterface PluginRegistryRecordV1 {\n  readonly id: string;\n  readonly name: string;\n  readonly version: string;\n  readonly publisher: string;\n  readonly source: \"bundled\" | \"local\" | \"marketplace\";\n  readonly trustTier: \"core\" | \"verified\" | \"community\" | \"local-dev\";\n  readonly enabled: boolean;\n  readonly state: \"installed\" | \"blocked\" | \"active\" | \"degraded\" | \"disabled\";\n  readonly stateReason?: string;\n  readonly grantedCapabilities: readonly string[];\n  readonly pendingCapabilities: readonly string[];\n  readonly surfaces: PluginSurfaceSummaryV1;\n  readonly health?: PluginHealthV1;\n  readonly installedAt: string;\n  readonly updatedAt: string;\n}\n```\n\nPersistence rules:\n\n- Bundled core plugins are discovered on daemon startup.\n- Discovery is idempotent.\n- Removing a bundled plugin from the binary marks it unavailable; it does not delete plugin-owned user data.\n- Disabled plugins do not contribute prompts or active surface metadata.\n- Blocked plugins expose a clear\n`stateReason`\n\n. - Degraded plugins remain registered and visible in diagnostics.\n\n## Lifecycle Contract\n\nV1 states:\n\n``` php\ninstalled -> blocked | disabled | active -> degraded\n```\n\nRules:\n\n- Unsupported runtime means\n`blocked`\n\n. - Missing dependency means\n`blocked`\n\n. - Health failure means\n`degraded`\n\n. - User/admin disable means\n`disabled`\n\n. `disabled`\n\nremoves prompt contributions and active surface metadata.`degraded`\n\ndoes not crash the daemon.- Core plugins may be non-removable but can still report degraded/disabled where safe.\n\n## Capability and Grant Contract\n\nCapabilities are declared by a manifest and granted by host policy.\n\nFor V1:\n\n- bundled core plugins may receive bundled grants,\n- unsupported plugins receive no grants,\n- marketplace/local installs are metadata-only and cannot execute,\n- capability checks are enforced for plugin-owned daemon routes where the host mounts them,\n- compatibility routes may continue using existing auth while recording their owning plugin in diagnostics.\n\nRequired `signet.secrets`\n\ncapabilities:\n\n```\nsecrets:list\nsecrets:write\nsecrets:delete\nsecrets:exec\nsecrets:providers:list\nsecrets:providers:configure\nprompt:contribute:user-prompt-submit\nmcp:tool\ncli:command\ndashboard:panel\nsdk:client\nconnector:capability\n```\n\nThe grant model must distinguish:\n\n```\ndeclaredCapabilities != grantedCapabilities\n```\n\nEven for bundled plugins, diagnostics should show both.\n\n## Surface Metadata Registry\n\nV1 stores and exposes surface metadata. It does not require every consumer to be fully dynamic yet.\n\nSurface metadata includes:\n\n```\ninterface PluginSurfaceSummaryV1 {\n  readonly daemonRoutes: readonly PluginRouteSummaryV1[];\n  readonly cliCommands: readonly PluginCommandSummaryV1[];\n  readonly mcpTools: readonly PluginToolSummaryV1[];\n  readonly dashboardPanels: readonly PluginDashboardSummaryV1[];\n  readonly sdkClients: readonly PluginSdkSummaryV1[];\n  readonly connectorCapabilities: readonly PluginConnectorSummaryV1[];\n  readonly promptContributions: readonly PluginPromptSummaryV1[];\n}\n```\n\nRules:\n\n- Disabled plugins have no active surface metadata.\n- Blocked plugins can show planned surfaces but not active surfaces.\n- Existing first-party CLI/MCP/dashboard surfaces may remain hand-wired but\nshould be represented in metadata under\n`signet.secrets`\n\n. - Surface metadata includes docs/help text.\n- Surface metadata never includes secret values or provider tokens.\n\n## Prompt Contribution Contract\n\nV1 supports static prompt contributions from bundled core plugins.\n\nContribution shape:\n\n```\ninterface PromptContributionV1 {\n  readonly id: string;\n  readonly pluginId: string;\n  readonly target: \"system\" | \"session-start\" | \"user-prompt-submit\";\n  readonly mode: \"append\" | \"context\";\n  readonly priority: number;\n  readonly maxTokens: number;\n  readonly content: string;\n}\n```\n\nOrdering bands:\n\n| Priority band | Owner |\n|---|---|\n| 0-99 | Signet core invariants |\n| 100-199 | user identity |\n| 200-299 | runtime/connectors |\n| 300-399 | memory |\n| 400-499 | plugin advisory context |\n\nRules:\n\n- V1 plugin contributions default to\n`400-499`\n\n. - Contributions are append/context only.\n- Contributions cannot suppress or replace user identity files.\n- Contributions are clipped to\n`maxTokens`\n\nbefore global prompt clipping. - Prompt diagnostics list included and excluded contributions.\n- Disabling the owning plugin removes the contribution without daemon restart if the prompt registry is re-read at request time, or after daemon restart if V1 implementation chooses startup-only registry loading. The chosen behavior must be documented.\n\nRequired Secrets contribution:\n\n```\nWhen the user provides credentials or a task requires reusable credentials,\nprefer storing them in Signet Secrets rather than chat, memory, logs, or source\nfiles. Use secret_exec or provider-backed secret references when commands need\ncredentials.\n```\n\n## Plugin Diagnostics API\n\nV1 adds daemon diagnostics endpoints. Exact paths may be adjusted to match route organization, but the response contracts must be stable.\n\nRequired endpoints:\n\n```\nGET /api/plugins\nGET /api/plugins/:id\nGET /api/plugins/:id/diagnostics\nGET /api/plugins/prompt-contributions\n```\n\n`GET /api/plugins`\n\nresponse:\n\n```\ninterface PluginListResponseV1 {\n  readonly plugins: readonly PluginRegistryRecordV1[];\n}\n```\n\n`GET /api/plugins/prompt-contributions`\n\nresponse:\n\n```\ninterface PromptContributionListResponseV1 {\n  readonly contributions: readonly PromptContributionV1[];\n  readonly activeCount: number;\n}\n```\n\nRules:\n\n- Diagnostics never include raw secret values.\n- Diagnostics identify disabled/blocked/degraded reasons.\n- Diagnostics identify active prompt contributors by plugin ID.\n- Diagnostics identify compatibility routes owned by plugins.\n\n## Secrets Plugin V1\n\n`signet.secrets`\n\nis a bundled privileged core plugin.\n\nIt owns metadata for:\n\n`/api/secrets/*`\n\nroutes,`signet secret`\n\nCLI commands,- Signet MCP secret tools,\n- dashboard Secrets settings panel,\n- SDK secret helpers,\n- connector-visible secret capabilities,\n- Secrets prompt contribution.\n\nV1 implementation may keep route/controller code in its current package layout\nif the plugin host records `signet.secrets`\n\nas the owner. The important V1\nchange is the capability boundary and local provider extraction, not a cosmetic\nfile move.\n\n## Local Secrets Provider Extraction\n\nThe current local encrypted store becomes a provider implementation under\n`signet.secrets`\n\n.\n\nProvider interface:\n\n```\ninterface LocalSecretProviderV1 {\n  readonly id: \"local\";\n  list(ctx: SecretContextV1): Promise<readonly SecretDescriptorV1[]>;\n  put(name: string, value: string, ctx: SecretContextV1): Promise<void>;\n  delete(name: string, ctx: SecretContextV1): Promise<boolean>;\n  resolve(ref: SecretRefV1, ctx: SecretContextV1): Promise<ResolvedSecretV1>;\n  health(ctx: SecretContextV1): Promise<SecretProviderHealthV1>;\n}\n```\n\nCompatibility invariant:\n\n```\nExisting $SIGNET_WORKSPACE/.secrets/secrets.enc files remain valid without\nmigration, re-encryption, relocation, or user action.\n```\n\nV1 must preserve:\n\n```\nfile:   $SIGNET_WORKSPACE/.secrets/secrets.enc\nformat: version 1 JSON wrapper with per-secret ciphertext\ncrypto: libsodium secretbox\nkey:    BLAKE2b-256 of signet:secrets:{machine-id}\n```\n\nRules:\n\n- Startup must not rewrite\n`secrets.enc`\n\n. - Listing secrets must not decrypt every value unless necessary.\n- Resolve happens only inside the daemon/plugin/provider boundary.\n- Command execution redacts resolved values from stdout/stderr.\n- Corrupt or machine-mismatched stores fail clearly and are never overwritten automatically.\n- Writes may update\n`secrets.enc`\n\nusing the existing format. - Existing bare names keep working as local references.\n\n## Secrets Compatibility Routes\n\nExisting routes remain available:\n\n```\nGET    /api/secrets\nPOST   /api/secrets/:name\nDELETE /api/secrets/:name\nPOST   /api/secrets/exec\nGET    /api/secrets/exec/:jobId\nPOST   /api/secrets/:name/exec\nGET    /api/secrets/1password/status\nPOST   /api/secrets/1password/connect\nDELETE /api/secrets/1password/connect\nGET    /api/secrets/1password/vaults\nPOST   /api/secrets/1password/import\n```\n\nV1 does not need to convert 1Password into a provider, but it must not regress\n1Password behavior. If 1Password remains on the current implementation path, the\nplugin diagnostics should mark it as compatibility-owned by `signet.secrets`\n\nand\nfuture-provider pending.\n\n## Secret Reference and Alias V1\n\nV1 must support:\n\n```\nOPENAI_API_KEY == local://OPENAI_API_KEY\n```\n\nProvider-qualified syntax for future providers may be accepted in parsers, but\nonly `local://`\n\nis required to resolve in V1.\n\nResolution order in V1:\n\n`local://NAME`\n\n- bare\n`NAME`\n\nas local compatibility lookup\n\nUser-defined aliases may be deferred. If implemented in V1, they must follow the broader planning spec rules: provider-qualified target, audit event, and loop rejection.\n\n## Audit Events V1\n\nV1 must emit audit or structured diagnostic events for:\n\n```\nplugin.discovered\nplugin.enabled\nplugin.disabled\nplugin.blocked\nplugin.degraded\nplugin.health_failed\nprompt.contribution_added\nprompt.contribution_removed\nsecret.listed\nsecret.stored\nsecret.deleted\nsecret.resolved_for_exec\nsecret.exec_started\nsecret.exec_completed\n```\n\nRules:\n\n- Secret values are never logged.\n- Command stdout/stderr are not audit payloads.\n- Event payloads include plugin ID, timestamp, result, and agent scope where available.\n- Secret names may be included only where current API behavior already exposes them or policy allows them.\n\n## Rollback and Degraded Mode\n\nV1 rollback depends on not rewriting user data.\n\nRules:\n\n- The plugin host migration does not rewrite\n`secrets.enc`\n\n. - If plugin registry loading fails, the daemon should still be able to mount existing secrets routes through the local provider compatibility path.\n- If\n`signet.secrets`\n\nis degraded, diagnostics must say whether local secrets are available, unavailable, or blocked by key mismatch/corruption. - If prompt contribution loading fails, prompt-submit continues without plugin contributions and records degraded diagnostics.\n- Disabling\n`signet.secrets`\n\nremoves prompt guidance and connector/MCP advertising, but must not delete stored secrets.\n\n## Implementation Phases\n\n### Phase 1: Host and Registry\n\n- Add manifest types and validation.\n- Add plugin registry persistence.\n- Discover bundled core plugins at startup.\n- Add\n`/api/plugins`\n\ndiagnostics. - Add lifecycle states and health status.\n\n### Phase 2: Prompt and Surface Metadata\n\n- Add prompt contribution registry.\n- Add prompt contribution diagnostics.\n- Add surface metadata registry.\n- Represent existing Secrets CLI/MCP/dashboard/SDK/connectors in metadata.\n\n### Phase 3: Secrets Plugin Metadata\n\n- Register\n`signet.secrets`\n\nas bundled core plugin. - Associate existing secrets routes and surfaces with\n`signet.secrets`\n\n. - Add Secrets prompt contribution.\n- Add enable/disable behavior for prompt and advertised surfaces.\n\n### Phase 4: Local Provider Extraction\n\n- Extract current local secret store behind provider interface.\n- Preserve existing encryption and file format.\n- Add compatibility fixtures for existing\n`secrets.enc`\n\n. - Keep all existing secrets routes passing.\n\n### Phase 5: Guardrails and Docs\n\n- Add audit events.\n- Add docs/help metadata.\n- Add CLI setup selection for bundled core plugins. Existing installs default\n`signet.secrets`\n\nto enabled; new interactive installs explain Signet Secrets and ask whether to enable it. - Add degraded-mode tests.\n- Update\n`docs/API.md`\n\n,`docs/SECRETS.md`\n\n,`docs/SDK.md`\n\n,`docs/MCP.md`\n\n, and dashboard docs where behavior or ownership changed.\n\n## Validation and Tests\n\nRequired tests:\n\n- manifest validation rejects invalid IDs, versions, missing docs metadata, and unsupported active runtimes.\n- bundled\n`signet.secrets`\n\nis discovered idempotently. `/api/plugins`\n\nlists`signet.secrets`\n\nwith expected state, capabilities, grants, and surfaces.- disabling\n`signet.secrets`\n\nremoves its prompt contribution. - prompt diagnostics list active contributions with plugin provenance.\n- prompt contribution clipping respects\n`maxTokens`\n\n. - plugin health failure reports degraded state without crashing daemon.\n- unsupported Rust sidecar manifest enters blocked state in V1.\n- v1\n`secrets.enc`\n\nfixture remains readable by local provider. - startup does not rewrite existing\n`secrets.enc`\n\n. - storing a new local secret writes the existing format.\n- corrupt\n`secrets.enc`\n\nfails clearly and is not overwritten. - machine-mismatched\n`secrets.enc`\n\nfails clearly and is not overwritten. `/api/secrets/*`\n\ncompatibility routes preserve existing behavior.`execWithSecrets`\n\ninjects resolved local values and redacts stdout/stderr.- ordinary API/MCP/dashboard/SDK responses do not include raw secret values.\n- 1Password compatibility routes do not regress.\n- setup registry tests prove new installs can persist\n`signet.secrets`\n\nenabled or disabled without disturbing unrelated plugin registry entries.\n\nRequired local commands before PR:\n\n```\nbun test platform/daemon/src/secrets*.test.ts\nbun test platform/daemon/src/plugin*.test.ts\nbun run typecheck\nbun run lint\n```\n\nThe exact test filenames may differ, but the PR must include regression tests for the contracts above.\n\n## Documentation Updates\n\nWhen implemented, update:\n\n`docs/API.md`\n\nfor plugin diagnostics routes and secrets ownership notes.`docs/SECRETS.md`\n\nfor`signet.secrets`\n\n, local provider compatibility, and the no-raw-secret-read invariant.`docs/SDK.md`\n\nto remove or correct any implication that ordinary SDK callers can retrieve raw secret values.`docs/MCP.md`\n\nto state that secret tools use injection/listing only and are plugin-owned.`docs/DASHBOARD.md`\n\nto describe plugin-owned Secrets settings and provider status.`docs/specs/INDEX.md`\n\nand`docs/specs/dependencies.yaml`\n\nwhen status changes.\n\n## Success Criteria\n\nThis spec is complete when:\n\n`signet.secrets`\n\nappears as a bundled core plugin in daemon diagnostics.- Existing secrets routes, CLI, MCP, dashboard, and SDK behavior continue to work.\n- Existing local\n`secrets.enc`\n\nfixtures pass without migration. - Secrets prompt contribution appears only when\n`signet.secrets`\n\nis enabled. - Plugin registry and surface metadata are visible through diagnostics.\n- Unsupported Rust/sidecar plugin metadata is blocked cleanly rather than executed or ignored silently.\n- Tests prove secret values are not exposed through ordinary responses.\n- Docs describe the plugin-owned Secrets architecture and compatibility guarantees.\n- CLI setup enables\n`signet.secrets`\n\nby default for existing installs, prompts new interactive installs in a Core plugins section, and supports non-interactive opt-out without deleting stored secrets.", "url": "https://wpnews.pro/news/plugin-sdk-core-v1-and-secrets-local-provider-extraction", "canonical_source": "https://signetai.sh/docs/specs/complete/plugin-sdk-core-v1/", "published_at": "2026-06-18 07:37:48+00:00", "updated_at": "2026-06-18 23:38:31.020842+00:00", "lang": "en", "topics": ["developer-tools", "ai-infrastructure"], "entities": ["Signet"], "alternates": {"html": "https://wpnews.pro/news/plugin-sdk-core-v1-and-secrets-local-provider-extraction", "markdown": "https://wpnews.pro/news/plugin-sdk-core-v1-and-secrets-local-provider-extraction.md", "text": "https://wpnews.pro/news/plugin-sdk-core-v1-and-secrets-local-provider-extraction.txt", "jsonld": "https://wpnews.pro/news/plugin-sdk-core-v1-and-secrets-local-provider-extraction.jsonld"}}