Skip to content
Draft
Show file tree
Hide file tree
Changes from 81 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
ca00b76
🤖 docs: add extension platform PRD
ThomasK33 May 4, 2026
2aaba7a
🤖 docs: address advisor review of extension platform PRD
ThomasK33 May 4, 2026
80739f3
chore: ralph setup
ThomasK33 May 4, 2026
bfaba1a
feat: US-002 - Define per-contribution descriptor schemas (available …
ThomasK33 May 4, 2026
c9a03f3
feat: US-004 - Implement Manifest Validator with reserved-prefix reje…
ThomasK33 May 4, 2026
54ceacd
feat: US-005 - Implement Permission Calculator with drift status
ThomasK33 May 4, 2026
82ae953
feat: US-009 - Implement Project-local Extension State Store
ThomasK33 May 4, 2026
6719494
feat: US-011 - Implement BundledExtensionRootResolver
ThomasK33 May 4, 2026
d00a968
feat: US-012 - Implement Extension Discovery Service (three-phase)
ThomasK33 May 4, 2026
994bef7
feat: US-013 - Implement Extension Registry Service
ThomasK33 May 4, 2026
b9edf64
feat: US-014 - Implement Root Watcher with debounce
ThomasK33 May 4, 2026
fb07124
feat: US-018 - Create Platform Demo Extension package
ThomasK33 May 4, 2026
799b5fd
feat: US-020 - Wire bundled extension root into Electron extraResources
ThomasK33 May 4, 2026
6bc5fa0
feat: US-021 - Implement Extensions Settings Section root layout
ThomasK33 May 4, 2026
7fe2e46
feat: US-022 - Implement Extension Card component
ThomasK33 May 4, 2026
f29eded
feat: US-023 - Implement Consent Shortcut Modal with granular fallback
ThomasK33 May 4, 2026
1f8397e
feat: US-025 - Implement diagnostics surfacing per matrix
ThomasK33 May 4, 2026
03eb12c
feat: US-026 - Add bun debug extensions command
ThomasK33 May 5, 2026
08d9060
feat: US-027 - Wire bundled root into ServiceContainer + e2e smoke fo…
ThomasK33 May 5, 2026
ba3207c
feat: US-028 - Add Extensions docs (authoring, telemetry, release-che…
ThomasK33 May 5, 2026
15efeee
chore: deslop + format pass across extension modules
ThomasK33 May 5, 2026
04b01c3
fix: auto-grant bundled extensions via Mux/Governor policy
ThomasK33 May 5, 2026
b437b4a
fix: backend isExperimentEnabled honors enabledByDefault when telemet…
ThomasK33 May 5, 2026
0844b53
fix: ExtensionRegistry recomputes requestedPermissionsHash authoritat…
ThomasK33 May 5, 2026
db74a51
fix: merge extension-contributed skills into agentSkills slash menu (…
ThomasK33 May 5, 2026
822a81c
fix: correct extension docs URL to mux.coder.com
ThomasK33 May 5, 2026
757168c
fix: render Extensions cheat-sheet trigger keybind as a kbd pill
ThomasK33 May 5, 2026
a57663a
fix: cheat-sheet modal panel uses defined bg-background-secondary token
ThomasK33 May 5, 2026
6a14467
fix: rename Demo Extension distribution id to @coder/mux-extension-pl…
ThomasK33 May 5, 2026
aa1edf4
fix: thread extension skills into slash dispatch + tool layer
ThomasK33 May 5, 2026
c93effe
fix: widen agent-skill scope enums to include 'extension'
ThomasK33 May 5, 2026
2b49ae9
fix: agent_skill_list surfaces extension-contributed skills
ThomasK33 May 5, 2026
6c3668a
🤖 refactor: deslop extension platform changes
ThomasK33 May 8, 2026
7822530
🤖 fix: keep extension platform static checks green
ThomasK33 May 8, 2026
f17f15e
🤖 fix: use validated extension skill sources
ThomasK33 May 8, 2026
782fdae
🤖 fix: address coder-agents extension review
ThomasK33 May 8, 2026
efc6c71
🤖 fix: complete extension root flows
ThomasK33 May 8, 2026
b829563
🤖 fix: complete extension root review fixes
ThomasK33 May 8, 2026
15c9e02
🤖 fix: address broad extension review
ThomasK33 May 8, 2026
d4de6a4
🤖 fix: close extension review gaps
ThomasK33 May 8, 2026
7e95e2e
🤖 fix: scope extension skills by project
ThomasK33 May 8, 2026
78bfd47
🤖 fix: address final extension review issues
ThomasK33 May 8, 2026
1756222
🤖 fix: close final extension review feedback
ThomasK33 May 8, 2026
155d9cd
🤖 fix: resolve final extension review items
ThomasK33 May 8, 2026
ab208df
🤖 fix: settle extension review edge cases
ThomasK33 May 8, 2026
9ad5b2d
🤖 fix: trap focus in extension dialogs
ThomasK33 May 8, 2026
1ed2b74
🤖 fix: handle extension skill files and palette gating
ThomasK33 May 8, 2026
c81aae4
🤖 fix: support extension skill file reads
ThomasK33 May 8, 2026
78f8003
🤖 fix: polish extension consent and stale actions
ThomasK33 May 8, 2026
b0159c7
🤖 fix: honor extension platform gates
ThomasK33 May 8, 2026
531b649
🤖 fix: scope extension settings focus
ThomasK33 May 8, 2026
ef64cad
🤖 fix: stabilize extension root paths
ThomasK33 May 8, 2026
210bd9c
🤖 fix: keep activated sibling contributions
ThomasK33 May 8, 2026
4040d88
🤖 fix: reload extensions on policy changes
ThomasK33 May 8, 2026
0bb87e1
🤖 fix: reload extensions on experiment changes
ThomasK33 May 8, 2026
0438d27
🤖 fix: preserve policy schema passthrough
ThomasK33 May 8, 2026
01f1869
🤖 fix: refresh renderer experiments on changes
ThomasK33 May 8, 2026
6c35de7
🤖 fix: trust project with extension root
ThomasK33 May 8, 2026
2f5ec11
🤖 fix: avoid regrant on version-only extension drift
ThomasK33 May 8, 2026
e2482e2
🤖 fix: reject grants for missing extensions
ThomasK33 May 8, 2026
7d37e78
🤖 fix: block renamed extension packages
ThomasK33 May 8, 2026
0090b76
🤖 fix: scope extension conflicts by project
ThomasK33 May 8, 2026
f54575a
🤖 fix: scope global extension grants
ThomasK33 May 8, 2026
5110a6b
🤖 fix: isolate project-local extension conflicts
ThomasK33 May 8, 2026
7beceb3
🤖 fix: isolate bundled extension state
ThomasK33 May 8, 2026
6aeff51
🤖 fix: isolate extension root failures
ThomasK33 May 8, 2026
5b4e6a9
🤖 fix: scope resolver diagnostics by root
ThomasK33 May 8, 2026
f47c679
🤖 fix: hide bundled extension toggles
ThomasK33 May 8, 2026
2e71700
🤖 fix: avoid inactive local skill shadowing
ThomasK33 May 8, 2026
d76d571
🤖 fix: contain extension dependency paths
ThomasK33 May 8, 2026
9606f9f
🤖 fix: harden extension discovery paths
ThomasK33 May 8, 2026
cb9ef5c
🤖 fix: read manifest-backed extension skills
ThomasK33 May 8, 2026
1a472ff
🤖 fix: advertise extension skills in streams
ThomasK33 May 8, 2026
bba5c01
🤖 fix: prefer project-local skill ids
ThomasK33 May 8, 2026
6111988
🤖 fix: ship bundled extensions in npm package
ThomasK33 May 8, 2026
ddd9b07
🤖 fix: recheck extension skill symlinks
ThomasK33 May 8, 2026
44b5865
🤖 fix: keep fresh extension grants normal
ThomasK33 May 8, 2026
e876fd6
🤖 fix: avoid discovery timeout test race
ThomasK33 May 8, 2026
6e23fa8
🤖 fix: validate contribution lists
ThomasK33 May 8, 2026
787f91f
🤖 fix: avoid starting timed-out discovery
ThomasK33 May 8, 2026
b533f79
🤖 fix: keep extension agents inspection-only
ThomasK33 May 8, 2026
84579a6
🤖 fix: mark extension agents inspection-only
ThomasK33 May 8, 2026
232d485
🤖 fix: surface extension state diagnostics
ThomasK33 May 8, 2026
26f0998
🤖 fix: skip stale grants for untrusted roots
ThomasK33 May 8, 2026
444fdf7
🤖 fix: reload before cached grant
ThomasK33 May 8, 2026
e328300
🤖 fix: grant from live extension manifest
ThomasK33 May 8, 2026
40baeda
🤖 fix: keep Governor policy schema strict
ThomasK33 May 9, 2026
5f87281
🤖 fix: address coder agents extension review
ThomasK33 May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .ralph-tui/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Ralph TUI Configuration
# Generated by setup wizard
# See: ralph-tui config help

configVersion = "2.1"

agent = "claude"
fallbackAgents = ["codex"]
tracker = "json"
maxIterations = 0
autoCommit = true
subagentTracingDetail = "moderate"

[agentOptions]
model = "opus"

[trackerOptions]
[parallel]
mode = "auto"
36 changes: 25 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ include fmt.mk
.PHONY: benchmark-terminal
.PHONY: ensure-deps rebuild-native mux
.PHONY: check-eager-imports check-bundle-size check-startup
.PHONY: bundled-extensions-validate bundled-extensions-build bundled-extensions-assemble

# Build tools
TSGO := bun run node_modules/@typescript/native-preview/bin/tsgo.js
Expand Down Expand Up @@ -130,7 +131,7 @@ rebuild-native: node_modules/.installed ## Rebuild native modules (node-pty, Duc
@echo "Native modules rebuilt successfully"

# Run compiled CLI with trailing arguments (builds only if missing)
mux: ## Run the compiled mux CLI (e.g., make mux server --port 3000)
mux: bundled-extensions-assemble ## Run the compiled mux CLI (e.g., make mux server --port 3000)
@test -f dist/cli/index.js -a -f dist/cli/api.mjs || $(MAKE) build-main
@node dist/cli/index.js $(filter-out $@,$(MAKECMDGOALS))

Expand All @@ -149,7 +150,7 @@ help: ## Show this help message

## Development
ifeq ($(OS),Windows_NT)
dev: node_modules/.installed build-main ## Start development server (Vite + nodemon watcher for Windows compatibility)
dev: node_modules/.installed build-main bundled-extensions-assemble ## Start development server (Vite + nodemon watcher for Windows compatibility)
@echo "Starting dev mode (3 watchers: nodemon for main process, esbuild for api, vite for renderer)..."
# On Windows, use npm run because bunx doesn't correctly pass arguments to concurrently
# https://github.com/oven-sh/bun/issues/18275
Expand All @@ -159,15 +160,15 @@ dev: node_modules/.installed build-main ## Start development server (Vite + node
'npx esbuild src/cli/api.ts $(ESBUILD_CLI_FLAGS) --watch' \
"vite"
else
dev: node_modules/.installed build-main build-preload ## Start development server (Vite + tsgo watcher for 10x faster type checking)
dev: node_modules/.installed build-main build-preload bundled-extensions-assemble ## Start development server (Vite + tsgo watcher for 10x faster type checking)
@bun x concurrently -k \
"bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \
'bun x esbuild src/cli/api.ts $(ESBUILD_CLI_FLAGS) --watch' \
"vite"
endif

ifeq ($(OS),Windows_NT)
dev-server: node_modules/.installed build-main ## Start server mode with hot reload (backend :3000 + frontend :5173). Use VITE_HOST=0.0.0.0 VITE_ALLOWED_HOSTS=<public-host> for remote access
dev-server: node_modules/.installed build-main bundled-extensions-assemble ## Start server mode with hot reload (backend :3000 + frontend :5173). Use VITE_HOST=0.0.0.0 VITE_ALLOWED_HOSTS=<public-host> for remote access
@echo "Starting dev-server..."
@echo " Backend (IPC/WebSocket): http://$(or $(BACKEND_HOST),127.0.0.1):$(or $(BACKEND_PORT),3000)"
@echo " Frontend (with HMR): http://$(or $(VITE_HOST),localhost):$(or $(VITE_PORT),5173)"
Expand All @@ -180,7 +181,7 @@ dev-server: node_modules/.installed build-main ## Start server mode with hot rel
"set NODE_ENV=development&& nodemon --watch dist/cli/index.js --watch dist/cli/server.js --delay 500ms dist/cli/index.js server --no-auth --host $(or $(BACKEND_HOST),127.0.0.1) --port $(or $(BACKEND_PORT),3000)" \
"set MUX_VITE_HOST=$(or $(VITE_HOST),127.0.0.1)&& set MUX_VITE_PORT=$(or $(VITE_PORT),5173)&& set MUX_VITE_ALLOWED_HOSTS=$(VITE_ALLOWED_HOSTS)&& set MUX_BACKEND_PORT=$(or $(BACKEND_PORT),3000)&& vite"
else
dev-server: node_modules/.installed build-main ## Start server mode with hot reload (backend :3000 + frontend :5173). Use VITE_HOST=0.0.0.0 VITE_ALLOWED_HOSTS=<public-host> for remote access
dev-server: node_modules/.installed build-main bundled-extensions-assemble ## Start server mode with hot reload (backend :3000 + frontend :5173). Use VITE_HOST=0.0.0.0 VITE_ALLOWED_HOSTS=<public-host> for remote access
@echo "Starting dev-server..."
@echo " Backend (IPC/WebSocket): http://$(or $(BACKEND_HOST),127.0.0.1):$(or $(BACKEND_PORT),3000)"
@echo " Frontend (with HMR): http://$(or $(VITE_HOST),localhost):$(or $(VITE_PORT),5173)"
Expand All @@ -202,11 +203,24 @@ dev-desktop-sandbox: ## Start an isolated Electron dev instance (fresh MUX_ROOT
dev-server-sandbox: ## Start an isolated dev-server instance (fresh MUX_ROOT + free ports)
@bun scripts/dev-server-sandbox.ts $(DEV_SERVER_SANDBOX_ARGS)

start: node_modules/.installed build-main build-preload build-static ## Build and start Electron app
start: node_modules/.installed build-main build-preload build-static bundled-extensions-assemble ## Build and start Electron app
@NODE_ENV=development bunx electron --remote-debugging-port=9222 .

## Bundled Extensions pipeline
# Source the bundled-extensions script for validate/build/assemble. Outputs land
# under build/extensions/ (the "<build>/extensions/" path electron-builder will
# pick up via extraResources in US-020).
bundled-extensions-validate: node_modules/.installed ## Validate each packages/<name> manifest via the production Manifest Validator
@bun scripts/bundled-extensions.ts validate

bundled-extensions-build: node_modules/.installed ## Run each bundled extension package's build script (no-op when absent)
@bun scripts/bundled-extensions.ts build

bundled-extensions-assemble: node_modules/.installed bundled-extensions-validate bundled-extensions-build ## Pack and extract bundled extensions into build/extensions/ (deterministic, offline)
@bun scripts/bundled-extensions.ts assemble

## Build targets (can run in parallel)
build: node_modules/.installed src/version.ts build-renderer build-main build-preload build-icons build-static ## Build all targets
build: node_modules/.installed src/version.ts build-renderer build-main build-preload build-icons build-static bundled-extensions-assemble ## Build all targets

build-main: node_modules/.installed dist/cli/index.js dist/cli/api.mjs ## Build main process

Expand Down Expand Up @@ -327,7 +341,7 @@ build/icon.png: docs/img/logo-white.svg scripts/generate-icons.ts
## Quality checks (can run in parallel)
# Keep the default local path fast. Docs link crawling and lockfile-free bench-agent
# verification stay in static-check-full so local validation remains responsive.
static-check: lint typecheck fmt-check check-eager-imports check-code-docs-links lint-shellcheck lint-hadolint ## Run fast local static checks
static-check: lint typecheck fmt-check check-eager-imports check-code-docs-links lint-shellcheck lint-hadolint bundled-extensions-validate ## Run fast local static checks

static-check-full: static-check check-bench-agent check-docs-links ## Run the full CI static check suite

Expand Down Expand Up @@ -431,11 +445,11 @@ check-deadcode: node_modules/.installed ## Check for potential dead code (manual
|| echo "✓ No obvious dead code found"

## Testing
test-integration: node_modules/.installed build-main ## Run all tests (unit + integration)
test-integration: node_modules/.installed build-main bundled-extensions-assemble ## Run all tests (unit + integration)
@bun test src
@TEST_INTEGRATION=1 bun x jest tests

test-unit: node_modules/.installed build-main ## Run unit tests
test-unit: node_modules/.installed build-main bundled-extensions-assemble ## Run unit tests
@bun test src
@bun test ./tests/ui/storybook/

Expand Down Expand Up @@ -595,7 +609,7 @@ benchmark-terminal: ## Run Terminal-Bench 2.0 with Harbor (use TB_HARBOR_PACKAGE
## Clean
clean: ## Clean build artifacts
@echo "Cleaning build artifacts..."
@rm -rf dist release build/icon.icns build/icon.png
@rm -rf dist release build/icon.icns build/icon.png build/extensions
@echo "Done!"

## Startup Performance Checks
Expand Down
14 changes: 14 additions & 0 deletions docs/adr/0001-permissions-are-requests-not-grants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Manifest permissions are requests, not grants

Mux extensions list permissions in their manifest's `requestedPermissions` field, but the manifest is author-controlled, so we treat those entries as **Requested Permissions** that Mux must explicitly grant before they become **Effective Permissions** used by capability queries. This split protects users from extensions self-authorizing dangerous capabilities (`shell.execute`, `network`, `desktop.capture`) just by listing them in a `package.json`, and aligns the model with browser/OS permission systems that distinguish requests from grants.

## Considered Options

- **Manifest permissions are grants.** Rejected: extension authors would be the trust boundary, contradicting how users actually think about installable code.
- **Registration vs operational permissions in different fields.** Rejected as over-engineered for v1; we instead derive registration permissions from declared contributions and keep operational permissions explicit in `requestedPermissions`.

## Consequences

- Mux must persist a separate **Grant Record** (keyed by **Extension Identity**) with drift metadata to detect package or permission changes.
- Newly requested permissions added by an extension update never become effective without a new grant.
- Bundled extensions still go through the same model, with **Core Extensions** as the only protected first-party escape hatch.
14 changes: 14 additions & 0 deletions docs/adr/0002-stable-v1-excludes-code-execution-surfaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Stable v1 manifest excludes extension code execution surfaces

Stable `manifestVersion: 1` defines a purely declarative contract: it has no `hostEntryPoints`, no `activationEvents`, no extension-authored handlers, and code-bearing contribution types (tools, runtime drivers, panels, agent lifecycle hooks, secret providers, MCP servers) are descriptor-only and remain inspection-only until Mux defines a separate **Host API Version** with a real sandbox/isolation model. We took this path because manifest fields published in v1 are effectively forever, and we are not yet ready to commit to an isolation model (V8 isolates vs. workers vs. processes), an IPC protocol, or per-extension resource limits.

## Considered Options

- **Ship `hostEntryPoints` (main/renderer/agent) now, gated behind a feature flag.** Rejected: even an "experimental" name in a stable manifest creates ossification pressure.
- **Provide declarative HTTP/webview tools as a substitute.** Rejected: that is code execution by another name, with worse security properties (network, SSRF, exfiltration) than a properly designed Host API.

## Consequences

- Useful v1 contribution types are limited to declarative ones: themes (token-based), layouts (importable templates), agents, skills, runtime presets, and commands targeting Mux-owned **Command Targets**.
- MCP server contributions sit alongside as the practical bridge for AI-callable tools, but are also inspection-only until import/enable UX exists.
- Future code execution will arrive through a new `manifestVersion` and an explicit **Host API Version**, with **Extension Lifecycle Triggers** designed alongside the sandbox.
15 changes: 15 additions & 0 deletions docs/adr/0003-extension-identity-vs-distribution-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Extension Identity is `mux.id`, distinct from Distribution Identity

The canonical identity of a Mux extension is its `mux.id` (a dotted reverse-domain string like `acme.kubernetes`), not the npm package name or version. Settings, enablement, and **Grant Records** are keyed by **Extension Identity**, while package name and version are kept as **Distribution Identity** metadata used for install/update and supply-chain drift detection.

## Considered Options

- **Key everything by npm package name.** Rejected: package renames would break user settings and grant history; reverse-domain notation also matches contribution-namespace expectations.
- **Compound identity `{ packageName, muxId }`.** Rejected: every consuming subsystem would have to reason about composite keys for marginal safety benefit over drift metadata.
- **Allow same-id duplicates as overrides.** Rejected; duplicate **Extension Identity** is treated as an **Extension Identity Conflict**, not an implicit replacement, until enforced **Extension Relationship Constraints** exist.

## Consequences

- Grants survive package renames and version bumps when the requested permission set is unchanged.
- Distribution-identity drift (different package, lower version, or new requested permissions) surfaces as inspection diagnostics and may force re-consent before grants apply to the new package.
- Future intentional replacement requires explicit, enforced relationship constraints rather than reusing a `mux.id`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# v1 of the extension platform is an additive release with a single demo extension and a default-on experiment kill switch

Stable v1 ships the extension platform itself — discovery, trust/grant model, contribution registries, and the Extensions Settings Section — but does **not** migrate any existing built-in feature into a bundled extension. The bundled extension root contains exactly one **Platform Demo Extension** that contributes a single new skill so discovery, **Activation Discovery**, contribution wiring, and the Extensions UI are exercised end-to-end without changing how any current feature behaves. The whole platform sits behind a default-on `EXTENSION_PLATFORM` experiment that, when disabled, cleanly hides the section, skips discovery, and preserves persisted **Grant Records** and trust state for clean re-enablement.

## Considered Options

- **Migration-first v1** (themes/layouts/agents/skills/1Password). Rejected: every migration requires hardened post-discovery fallbacks (theme paint races, runtime preset compat, 1Password resolver wiring) that we want to validate against a live platform first. A regression in cold-start theming or runtime resolution would affect every user.
- **Empty bundled root** (no demo extension). Rejected: leaves the entire contribution wiring untested in the shipped artifact and gives third-party authors no canonical reference.
- **First-class rollout without an experiment flag**. Rejected: the platform is a substantial new subsystem; we want a single switch to disable the entire surface without an emergency release if a critical bug lands.
- **Multi-contribution demo extension** (skills + theme + runtime preset + command). Rejected for v1 because it widens the failure surface of the demo itself; a skill-only demo keeps the validation signal pure.

## Consequences

- The v1 manifest contract (`manifestVersion: 1`, `requestedPermissions`, `Extension Identity` regex, closed `contributes` shape, no `hostEntryPoints`/`activationEvents`/relationship fields) is effectively frozen on release; subsequent platform releases evolve through **Descriptor Versions** and additive optional fields.
- Settings UI for v1 is consolidated in a single **Extensions Settings Section**; cross-section integration in General/Runtimes/Secrets/Layouts is deferred until migration releases populate those sections meaningfully.
- The Demo Extension's only responsibilities are: (a) keep discovery exercised on every cold start, (b) provide a canonical reference implementation for third-party authors, and (c) make the new section non-empty by default. It is not a `Core Extension` and users may disable it.
- The kill switch (`EXTENSION_PLATFORM` experiment + planned Governor policy hook) lets us roll back the platform surface via flag without altering persisted state, so users can re-enable without losing prior trust or grants.
- Migration of any built-in feature (themes, layouts, bundled agents, skills, 1Password, runtimes, browser, desktop) is explicitly future work and requires its own design pass per release; v1 does not commit to an order or timeline.
18 changes: 18 additions & 0 deletions docs/adr/0005-v1-platform-security-boundaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# v1 platform security boundaries: reserved identities, provenance-gated telemetry, display-only snapshot cache

The v1 extension platform enforces three layered security boundaries that together prevent third-party Extensions from impersonating first-party capabilities, exfiltrating data via telemetry, or affecting capability decisions before live validation completes. (1) The `mux` and `mux.*` Extension Identity prefix is the **Reserved Extension Identity Prefix** and is enforced by the Manifest Validator at discovery time: non-bundled roots claiming the prefix are rejected with `extension.identity.reserved` and contribute nothing. (2) **Provenance-gated Telemetry** requires both the reserved-prefix regex match AND `rootKind === "bundled"` for any identifier-field emission, so a regression in (1) does not regress (2). (3) The **Snapshot Cache** populates only the **Inspection Path**; the **Capability Path** returns empty or built-in-only results until live discovery recomputes Effective Permissions, so a stale or attacker-influenced cache cannot grant authority. v1 also drops the manifest `icon` field entirely until a sanitization design exists.

## Considered Options

- **Single-layer enforcement (regex-only or provenance-only).** Rejected: a regression in either gate would expose the platform; defense-in-depth keeps the boundary intact even if one mechanism fails.
- **Allowing arbitrary `Extension Identity` patterns from any root.** Rejected: third-party Extensions could squat the `mux.*` namespace and inherit telemetry-identity privileges or impersonate first-party for users who only inspect display names.
- **Trusting the cached snapshot for capability decisions.** Rejected: cache invalidation is best-effort across processes and across upgrade boundaries; decoupling the cache from the capability path means cache freshness only affects rendering speed, never authority.
- **Allowing package-provided extension icons in v1 with sanitization deferred.** Rejected: SVG rendering is in scope of the project's renderer-XSS rules; shipping `icon` without a sanitization design would either ossify an insecure surface or force a v1 retraction. Generic icons are acceptable for v1 inspection UI.

## Consequences

- The Manifest Validator takes `rootKind` as input and rejects reserved-prefix claims from non-bundled roots with a stable `extension.identity.reserved` diagnostic.
- The Extension Telemetry Layer requires both gates (reserved-prefix regex AND `rootKind === "bundled"`) for any `extensionId` / `contributionId` field; a unit test asserts that no event payload from a non-bundled root contains identifying string fields, even when the value matches the reserved regex.
- The Registry Service exposes separate **Inspection Path** and **Capability Path** read APIs; capability queries against an unconfirmed snapshot return empty (or built-in-only) and the consuming subsystem applies its **Migration Fallback** (when applicable).
- **Snapshot Cache** invalidation includes the mtimes/hashes of **Global Extension State** and **Project-local Extension State** files, so trust/grant changes never persist as a stale Inspection Path render across processes or upgrades.
- The manifest `icon` field is removed from v1; inspection UI uses generic icons. Authors who supply `icon` get an `info`-severity `manifest.unknown_field` diagnostic. Adding package-provided iconography requires a future sanitization design and an explicit ADR.
8 changes: 8 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@
"integrations/acp"
]
},
{
"group": "Extensions",
"pages": [
"extensions/authoring",
"extensions/telemetry",
"extensions/release-checklist"
]
},
{
"group": "Reference",
"pages": [
Expand Down
Loading
Loading