Appearance
Archived — terminal-state snapshot. This document captures a historical migration or audit and is not active guidance. Current canonical documentation lives in the repo root
README.md, the per-package READMEs underpackages/, and the typedoc-generated reference atdocs/api/. Retained for historical reference only.
v1.2 NIP-5D Drift Audit
Milestone: v1.2 — NIP-5D Conformance & Full NAP Coverage Audited: 2026-04-17 Spec source of truth: specs/NIP-5D.md — synced from dskvr/nips branch nip/5d. Napplet NAP source of truth: eight @napplet/nap-* packages — identity, inc, keys, media, notify, relay, storage, theme.
Purpose
This document is the per-package inventory of every way @kehto diverges from:
- The canonical NIP-5D spec text (
specs/NIP-5D.md). - The message surface exported by the eight
@napplet/nap-*packages.
Every drift item has a stable ID (DRIFT-{ACL,RT,SHELL,SVC,CORE}-NN), a source path, a one-line remediation note, and a Target Phase. Downstream Phase 12/13/14 planners generate plans mechanically from this table — they cite DRIFT-* IDs directly and do not re-audit source.
Canonical-Spec Deltas (v1.2)
Three deltas vs. the v1.1 snapshot drive most of the shell-side drift rows:
window.nostrMUST NOT be provided to napplet iframes (specs/NIP-5D.mdline 44 + Security §6). Reverses v1.1SH-I02— any shell-side injection must be removed.shell.supports()uses namespaced capability strings —perm:<permission>for sandbox permissions (e.g.,perm:popups), bare ornap:for NAP capabilities (specs/NIP-5D.mdlines 81-94).- Signing and encryption are shell-mediated via
relay.publish/relay.publishEncrypted— no napplet-sidesigner.*messages exist in canonical NIP-5D. The formersignerdomain is dissolved into read-onlyidentity(for public info) plus shell-internal signing inside relay publishes.
Target Phase Mapping
| Target Phase | Scope |
|---|---|
| 11 | Peer-dep prerequisites (@napplet/core bump, eight nap peer deps declared) |
| 12 | Shell conformance + seven non-theme nap coverage + ACL mapping for all 8 domains + signer-domain removal |
| 13 | Theme NAP end-to-end implementation (runtime route, reference service, shell adapter) |
| 14 | Replace hand-rolled dispatch switch with @napplet/core createDispatch() / registerNap() |
ID Legend
| Namespace | Scope |
|---|---|
| DRIFT-ACL-NN | @kehto/acl (packages/acl/src/) |
| DRIFT-RT-NN | @kehto/runtime (packages/runtime/src/) |
| DRIFT-SHELL-NN | @kehto/shell (packages/shell/src/) |
| DRIFT-SVC-NN | @kehto/services (packages/services/src/) |
| DRIFT-CORE-NN | Cross-cutting — dispatch / core-API / peer-dep prerequisite |
1. @kehto/acl
@kehto/acl owns NAP-domain capability resolution for all eight canonical domains. The single entry point is resolveCapabilitiesNap() in packages/acl/src/resolve.ts; capability gating happens when @kehto/runtime calls this function before dispatching an inbound envelope. Drift here means capability lookups return null/null (silent pass-through) for new domains and still recognise the removed signer domain.
| ID | Drift Item | Current State | Spec/Package Requirement | Target Phase | Remediation Note |
|---|---|---|---|---|---|
| DRIFT-ACL-01 | resolveCapabilitiesNap has no case 'identity' — identity.* falls through to default-null | packages/acl/src/resolve.ts:105 switch — only relay/signer/storage/inc/theme cases | NAP-03, NAP-10 | 12 | Add case 'identity': with per-action mapping for the 9 identity requests. Resolved in Plan 12-10. |
| DRIFT-ACL-02 | resolveCapabilitiesNap has no case 'keys' | packages/acl/src/resolve.ts:105 — no keys branch (no file exists) | NAP-05, NAP-10 | 12 | Add case 'keys': covering forward, registerAction, unregisterAction. Resolved in Plan 12-10. |
| DRIFT-ACL-03 | resolveCapabilitiesNap has no case 'media' | packages/acl/src/resolve.ts:105 — no media branch | NAP-06, NAP-10 | 12 | Add case 'media': covering session.*, state, capabilities. Resolved in Plan 12-10. |
| DRIFT-ACL-04 | resolveCapabilitiesNap has no case 'notify' | packages/acl/src/resolve.ts:105 — no notify branch | NAP-07, NAP-10 | 12 | Add case 'notify': with default-prompt semantics; user-visible surface. Resolved in Plan 12-10. |
| DRIFT-ACL-05 | resolveCapabilitiesNap still has case 'signer' at line 109 | packages/acl/src/resolve.ts:109-115 — resolves getPublicKey/getRelays/nip04/nip44/signEvent | NAP-02, NAP-10 | 12 | Remove case 'signer': entirely; migrate getPublicKey/getRelays references to identity; drop nip04/nip44/signEvent. Resolved in Plan 12-10. |
| DRIFT-ACL-06 | relay ACL does not distinguish relay.publishEncrypted from relay.publish | packages/acl/src/resolve.ts:105-108 — only action==='publish' branch for relay:write | NAP-08, SH-C03 | 12 | Split relay branch: publishEncrypted requires a stricter relay:write + sign:nip44 composite gate. Resolved in Plan 12-10. |
| DRIFT-ACL-07 | inc capability mapping predates the channel sub-protocol (inc.channel.*) | packages/acl/src/resolve.ts:120-123 — only action==='emit' branched for write; channel messages flow through the non-emit branch | NAP-04, NAP-10 | 12 | Extend inc branch to cover channel.open, channel.emit, channel.broadcast, channel.list, channel.close with open-time ACL semantics. Resolved in Plan 12-10. |
| DRIFT-ACL-08 | storage ACL lacks coverage for keys.result discrimination and the eliminated storage.clear capability | packages/acl/src/resolve.ts:116-119 — get|keys → state:read / default → state:write; clear still in default bucket though no napplet message exists | NAP-09, NAP-10 | 12 | Narrow storage branch to the 4 canonical requests (get, set, remove, keys); drop clear support. Resolved in Plan 12-10. |
| DRIFT-ACL-09 | Identity read ACL has no distinction for per-query sensitivity (getPublicKey vs getBadges) | packages/acl/src/resolve.ts — no identity case yet (DRIFT-ACL-01) | NAP-03, NAP-10 | 12 | Within new identity case, map getPublicKey/getRelays → null (shell-public) and getProfile/getFollows/getList/getZaps/getMutes/getBlocked/getBadges → relay:read. Resolved in Plan 12-10. |
2. @kehto/runtime
@kehto/runtime is the protocol engine. The central dispatch point is a hand-rolled switch (domain) in packages/runtime/src/runtime.ts:744-749. Drift here means inbound envelopes for five of eight canonical domains fall through the default branch and are silently dropped.
| ID | Drift Item | Current State | Spec/Package Requirement | Target Phase | Remediation Note |
|---|---|---|---|---|---|
| DRIFT-RT-01 | Dispatch switch has no case 'identity' | packages/runtime/src/runtime.ts:744-749 — cases are relay/signer/storage/inc | NAP-03 | 12 | Add case 'identity': return handleIdentityMessage(windowId, envelope); and wire to a new identity handler. Resolved in Plan 12-03. |
| DRIFT-RT-02 | Dispatch switch has no case 'keys' | packages/runtime/src/runtime.ts:744-749 | NAP-05 | 12 | Add case 'keys': routed to a keys handler that forwards to @kehto/services keys-service and to hooks.hotkeys.executeHotkeyFromForward. Resolved in Plan 12-05. |
| DRIFT-RT-03 | Dispatch switch has no case 'media' | packages/runtime/src/runtime.ts:744-749 | NAP-06 | 12 | Add case 'media': routed to @kehto/services media-service. Resolved in Plan 12-06. |
| DRIFT-RT-04 | Dispatch switch has no case 'notify' | packages/runtime/src/runtime.ts:744-749 | NAP-07 | 12 | Add case 'notify': routed to @kehto/services notify-service. Resolved in Plan 12-07. |
| DRIFT-RT-05 | Dispatch switch has no case 'theme' | packages/runtime/src/runtime.ts:744-749 | TH-01 | 13 | Add case 'theme': routed to @kehto/services theme-service and wire a theme.changed broadcast path. Status: ✅ Resolved in Phase 13 (Plan 13-01). |
| DRIFT-RT-06 | Dispatch switch has case 'signer' at line 746 — canonical spec has no signer domain | packages/runtime/src/runtime.ts:746 — routes to handleSignerMessage | NAP-02 | 12 | Remove case 'signer': and handleSignerMessage(); functionality split between new identity handler and shell-internal signing inside relay.publish/publishEncrypted. Resolved in Plan 12-03. |
| DRIFT-RT-07 | Shell-proxy branch exposes signer.* as a postMessage-addressable surface | packages/runtime/src/runtime.ts:593-673 — handleSignerMessage() handles signer.signEvent, signer.nip04.encrypt/decrypt, signer.nip44.encrypt/decrypt | SH-C03 | 12 | Delete handleSignerMessage(); encryption primitives become private helpers called by handleRelayMessage for relay.publishEncrypted. Resolved in Plan 12-03. |
| DRIFT-RT-08 | Relay handler does not route relay.publishEncrypted separately from relay.publish | packages/runtime/src/runtime.ts:547-576 — case 'publish': only; no case 'publishEncrypted': | NAP-08 | 12 | Add case 'publishEncrypted': in the relay switch that encrypts content using the shell signer, then re-enters the publish path; emits relay.publishEncrypted.result. Resolved in Plan 12-08. |
| DRIFT-RT-09 | inc.channel.* messages have no explicit routing — inc handler pre-dates the channel sub-protocol | packages/runtime/src/runtime.ts:684-719 — only handles emit, subscribe, unsubscribe; channel.open, channel.emit, channel.broadcast, channel.list, channel.close, channel.closed hit default: break | NAP-04 | 12 | Extend handleIncMessage with a channel registry: open/close lifecycle, sender-excluded emit, broadcast to all open channels, list query. Resolved in Plan 12-04. |
| DRIFT-RT-10 | Signer service-registry special case in runtime still lookups services.get('signer') | packages/runtime/src/runtime.ts:607-614 — signerService branch short-circuits the internal signer fallback | NAP-02 | 12 | Remove the signer service-registry branch (tied to DRIFT-RT-07). Resolved in Plan 12-03. |
3. @kehto/shell
@kehto/shell is the browser adapter: it owns window.nostr injection, the shell.supports() capability query, and the per-domain proxies. This is where the three canonical-spec deltas land.
| ID | Drift Item | Current State | Spec/Package Requirement | Target Phase | Remediation Note |
|---|---|---|---|---|---|
| DRIFT-SHELL-01 | window.nostr is injected into napplet iframes | packages/shell/src/shell-init.ts:103 — window.nostr = { getPublicKey, signEvent, getRelays, nip04, nip44 }; bootstrap emitted by generateNostrBootstrap() (lines 50-131) | SH-C01 | 12 | Delete generateNostrBootstrap() and all references; add a regression test asserting window.nostr === undefined inside the sandbox; drop the srcdoc bootstrap path. Resolved in Plan 12-01. |
| DRIFT-SHELL-02 | shell.supports() capability list uses bare permission names instead of perm:<permission> namespace | packages/shell/src/shell-init.ts:42-47 — buildShellCapabilities() returns { naps: [...], sandbox: [] }; callers pass bare names | SH-C02 | 12 | Route sandbox-permission checks through a perm: prefix; keep bare names only for NAP-capability lookups (nap:<domain> optional). Resolved in Plan 12-02. |
| DRIFT-SHELL-03 | Shell does not mediate signing/encryption — napplets reach signing primitives via postMessage | packages/shell/src/shell-init.ts:103-128 — bootstrap exposes signEvent/nip04/nip44; ShellBridge at packages/shell/src/shell-bridge.ts:165-170 advertises signer capability | SH-C03 | 12 | Move signing into shell-owned relay-publish paths; remove signer proxy surface; shell signs internally inside relay.publish/relay.publishEncrypted. Resolved in Plan 12-01. |
| DRIFT-SHELL-04 | Shell-init declares 'signer' as a NAP capability | packages/shell/src/shell-init.ts:43 — const naps: string[] = ['signer', 'storage', 'inc']; | SH-C03, NAP-02 | 12 | Replace with canonical 8-domain list (['relay','identity','storage','inc','theme','keys','media','notify']); gate each by hook availability. Resolved in Plan 12-01. |
| DRIFT-SHELL-05 | Shell has no theme adapter API for broadcasting theme changes to napplets | packages/shell/src/ — no theme-proxy.ts; shell-bridge.ts has no theme.set / theme.changed path | TH-03 | 13 | Add a documented shell.theme.set(theme) API that emits theme.changed envelopes to registered napplets. Status: ✅ Resolved in Phase 13 (Plan 13-02) — bridge.publishTheme(theme) on ShellBridge broadcasts theme.changed to every registered napplet via runtime.sessionRegistry.getAllEntries() + originRegistry.getIframeWindow(). |
| DRIFT-SHELL-06 | Shell lacks a keys-forwarding adapter path — napplets receive no keyboard events from the host | packages/shell/src/ — no keys proxy; hotkeyHooks.executeHotkeyFromForward exists for the reverse direction only (napplet→shell) | NAP-05 | 12 | Add shell-side forwarder that maps host keydowns to keys.forward / keys.action envelopes per ACL; register a keys-proxy module. Resolved in Plan 12-11. |
| DRIFT-SHELL-07 | Shell re-exports SignerProxy/signer bindings via barrel index.ts | packages/shell/src/index.ts:15-45 — re-exports types and the signer-related NIP-07 facade from shell-init | SH-C03, NAP-02 | 12 | Remove signer exports; add identity-proxy, relay-proxy (incl. publishEncrypted), theme-proxy, keys-proxy, media-proxy, notify-proxy exports to mirror storage-proxy pattern. Resolved in Plan 12-11. |
| DRIFT-SHELL-08 | Shell has no per-domain proxy module for the five missing domains (identity, theme, keys, media, notify) | packages/shell/src/ — only shell-bridge + shell-init + topic + utility modules; no {identity,theme,keys,media,notify}-proxy.ts files | NAP-03, NAP-05, NAP-06, NAP-07, TH-03 | 12 | Create per-domain proxy modules following the existing storage-proxy shape; wire each into shell-bridge's dispatch. Resolved in Plan 12-11 (Phase 13 will compose theme-proxy into the theme runtime/service). |
4. @kehto/services
@kehto/services is the reference service layer. Drift here means reference handlers are missing for five domains, the legacy signer-service must be split/deleted, and co-located tests must follow suit.
| ID | Drift Item | Current State | Spec/Package Requirement | Target Phase | Remediation Note |
|---|---|---|---|---|---|
| DRIFT-SVC-01 | signer-service.ts implements the removed signer.* domain | packages/services/src/signer-service.ts (entire file, lines 77-220) — descriptor signer v1.0.0, handles getPublicKey, signEvent, getRelays, nip04.encrypt/decrypt, nip44.encrypt/decrypt; re-exported in packages/services/src/index.ts:45 | NAP-02, NAP-03, SH-C03 | 12 | Migrate getPublicKey/getRelays into new packages/services/src/identity-service.ts; DELETE signEvent, nip04.encrypt/decrypt, nip44.encrypt/decrypt (no napplet-visible messages remain — shell signs internally). Resolved in Plan 12-03. |
| DRIFT-SVC-02 | No identity-service.ts — identity.* (9 requests) has no reference handler | packages/services/src/ — no file exists yet | NAP-03 | 12 | Create identity-service.ts implementing getPublicKey, getRelays, getProfile, getFollows, getList, getZaps, getMutes, getBlocked, getBadges with their .result envelopes; register under domain identity. Resolved in Plan 12-03. |
| DRIFT-SVC-03 | No keys-service.ts — keys.* (6 messages) has no reference handler | packages/services/src/ — no file exists yet | NAP-05 | 12 | Create keys-service.ts implementing forward (fire-and-forget to hooks.hotkeys), registerAction/.result, unregisterAction, bindings push, action push. Resolved in Plan 12-05. |
| DRIFT-SVC-04 | No media-service.ts — media.* (8 messages) has no reference handler | packages/services/src/ — audio-service.ts exists but is wired to inc.emit on audio:* topics (not under the canonical media domain) | NAP-06 | 12 | Create media-service.ts covering session.create/.result, session.update, session.destroy, state, capabilities, command, controls; decide whether audio-service.ts becomes an internal engine for media-service or is deprecated. Resolved in Plan 12-06. |
| DRIFT-SVC-05 | No notify-service.ts — notify.* (11 messages) has no reference handler | packages/services/src/notification-service.ts exists but operates on inc.emit / legacy API (not under canonical notify domain) | NAP-07 | 12 | Create (or rename) notify-service.ts covering send/.result, dismiss, badge, channel.register, permission.request/.result, action, clicked, dismissed, controls; register under domain notify. Resolved in Plan 12-07. |
| DRIFT-SVC-06 | No theme-service.ts — theme.* (3 messages) has no reference handler | packages/services/src/ — no file exists yet | TH-02 | 13 | Create theme-service.ts implementing theme.get/.result and shell-initiated theme.changed broadcast. Status: ✅ Resolved in Phase 13 (Plan 13-01). |
| DRIFT-SVC-07 | packages/services/src/signer-service.test.ts exercises the removed signer.* surface | packages/services/src/signer-service.test.ts — full co-located test suite against createSignerService | DEPS-03 | 12 | Migrate the getPublicKey/getRelays cases to the new identity-service.test.ts; delete the signEvent/nip04/nip44 cases with rationale (no napplet-visible signing in canonical NIP-5D). Resolved in Plan 12-03. |
| DRIFT-SVC-08 | No relay-service that exposes a canonical relay.publishEncrypted path | packages/services/src/relay-pool-service.ts — handles relay.subscribe, relay.close, relay.publish only; no publishEncrypted | NAP-08 | 12 | Extend relay-pool-service.ts (or add sibling module) so the runtime can delegate relay.publishEncrypted — service encrypts via shell signer, then delegates to publish path. Resolved in Plan 12-08. |
5. Dispatch / Core API (Cross-cutting)
These drift items span multiple packages. They track the move from a hand-rolled dispatch switch to @napplet/core's formal dispatch API and the peer-dep prerequisites that unblock Phase 12+ work.
| ID | Drift Item | Current State | Spec/Package Requirement | Target Phase | Remediation Note |
|---|---|---|---|---|---|
| DRIFT-CORE-01 | Runtime hand-rolls a domain switch rather than delegating to @napplet/core's createDispatch() / registerNap() / dispatch() | packages/runtime/src/runtime.ts:744-749 — switch(domain) | DISPATCH-01, DISPATCH-03 | 14 | After all eight domain handlers land (Phase 12/13), replace the switch with dispatch(envelope) and remove the per-domain case statements. |
| DRIFT-CORE-02 | No runtime entry-point calls registerNap() for any of the 8 domains — handlers are attached via switch branches instead | packages/runtime/src/runtime.ts:444-720 — each domain handler is a bespoke function (handleRelayMessage, handleSignerMessage, handleStorageMessage, handleIncMessage); no registerNap call anywhere in the package | DISPATCH-02 | 14 | At createRuntime() startup, call registerNap('<domain>', handler) for each of the 8 domains; feed all handlers into createDispatch(). |
| DRIFT-CORE-03 | kehto packages peer-depend on @napplet/core at >=0.1.0 — must bump to ^0.2.0 | packages/{acl,runtime,shell,services}/package.json — each declares "@napplet/core": ">=0.1.0" under peerDependencies | DEPS-01 | 11 | Bump peer range to ^0.2.0 across all four packages; add changesets; verify workspace resolution still works. |
| DRIFT-CORE-04 | None of the eight @napplet/nap-* packages appear in any @kehto/* package.json peerDependencies block | packages/{acl,runtime,shell,services}/package.json — no @napplet/nap-identity, @napplet/nap-inc, @napplet/nap-keys, @napplet/nap-media, @napplet/nap-notify, @napplet/nap-relay, @napplet/nap-storage, @napplet/nap-theme entries | NAP-01 | 11 | Add all eight nap peer-deps uniformly to each kehto package; types-only consumption (no runtime import footprint expected). |
| DRIFT-CORE-05 | Hand-copied NAP message types live inside kehto; no imports from @napplet/nap-* exist yet | packages/runtime/src/runtime.ts:12 imports only @napplet/core — NAP message types are inferred from any casts (e.g., msg as any), not from the canonical @napplet/nap-* declarations | NAP-02 | 11 | After DRIFT-CORE-04, replace inline type inferences with imports from the appropriate @napplet/nap-<domain> package. |
6. 8-Domain Coverage Matrix
Flat matrix of canonical domain-prefixed messages against @kehto/runtime dispatch, @kehto/services reference handler, and @kehto/acl mapping. Every row cites the DRIFT-* IDs that track its resolution.
| Domain.Message | Runtime Dispatch | Service Handler | ACL Mapping | Notes |
|---|---|---|---|---|
| identity.getPublicKey | missing (DRIFT-RT-01) | missing (DRIFT-SVC-02) | missing (DRIFT-ACL-01, DRIFT-ACL-09) | Read-only; currently served via legacy signer.getPublicKey path. |
| identity.getRelays | missing (DRIFT-RT-01) | missing (DRIFT-SVC-02) | missing (DRIFT-ACL-01, DRIFT-ACL-09) | Currently served via legacy signer.getRelays path. |
| identity.getProfile | missing (DRIFT-RT-01) | missing (DRIFT-SVC-02) | missing (DRIFT-ACL-01, DRIFT-ACL-09) | No path at all today. |
| identity.getFollows | missing (DRIFT-RT-01) | missing (DRIFT-SVC-02) | missing (DRIFT-ACL-01, DRIFT-ACL-09) | No path. |
| identity.getBadges | missing (DRIFT-RT-01) | missing (DRIFT-SVC-02) | missing (DRIFT-ACL-01, DRIFT-ACL-09) | No path. |
| relay.subscribe | present (packages/runtime/src/runtime.ts:452, case 'subscribe') | relay-pool-service | present (resolve.ts:105-108, relay:read) | Spec-conformant today. |
| relay.publish | present (packages/runtime/src/runtime.ts:547, case 'publish') | relay-pool-service | present (relay:write + relay:read) | Accepts napplet-signed events; must migrate to shell-mediated signing (DRIFT-RT-07, DRIFT-SHELL-03). |
| relay.publishEncrypted | not routed separately (DRIFT-RT-08) | missing (DRIFT-SVC-08) | not gated separately (DRIFT-ACL-06) | Canonical NIP-5D DM path — highest priority for v1.2. |
| relay.query | present (packages/runtime/src/runtime.ts:578, case 'query') | cache-service | present (relay:read) | Spec-conformant. |
| keys.forward | missing (DRIFT-RT-02) | missing (DRIFT-SVC-03) | missing (DRIFT-ACL-02) | Fire-and-forget; high-frequency path. |
| keys.registerAction | missing (DRIFT-RT-02) | missing (DRIFT-SVC-03) | missing (DRIFT-ACL-02) | Needs result envelope. |
| keys.action | missing (DRIFT-RT-02) | missing (DRIFT-SVC-03) | missing (DRIFT-ACL-02) | Shell-initiated push path. |
| media.session.create | missing (DRIFT-RT-03) | missing (DRIFT-SVC-04) | missing (DRIFT-ACL-03) | Needs result envelope. |
| media.command | missing (DRIFT-RT-03) | missing (DRIFT-SVC-04) | missing (DRIFT-ACL-03) | Shell-initiated push path. |
| media.state | missing (DRIFT-RT-03) | missing (DRIFT-SVC-04) | missing (DRIFT-ACL-03) | High-frequency state report. |
| notify.send | missing (DRIFT-RT-04) | missing (DRIFT-SVC-05) | missing (DRIFT-ACL-04) | User-visible surface; prompt default. |
| notify.permission.request | missing (DRIFT-RT-04) | missing (DRIFT-SVC-05) | missing (DRIFT-ACL-04) | Asynchronous permission flow. |
| notify.clicked | missing (DRIFT-RT-04) | missing (DRIFT-SVC-05) | missing (DRIFT-ACL-04) | Shell-initiated push path. |
| storage.get | present (packages/runtime/src/runtime.ts:675-677, handleStorageMessage) | state-handler (storage-nap) | present (state:read) | Spec-conformant. |
| storage.set | present (packages/runtime/src/runtime.ts:675-677) | state-handler | present (state:write) | Spec-conformant. |
| storage.keys | present (packages/runtime/src/runtime.ts:675-677) | state-handler | present (state:read) | Spec-conformant. |
| inc.emit | present (packages/runtime/src/runtime.ts:685, case 'emit') | inline inc handler | present (resolve.ts:121-122, relay:write + relay:read) | Spec-conformant for topic emit. |
| inc.subscribe | present (packages/runtime/src/runtime.ts:700, case 'subscribe') | inline inc handler | present (relay:read) | Spec-conformant. |
| inc.channel.open | not routed explicitly (DRIFT-RT-09) | no channel handler | not gated for channel sub-protocol (DRIFT-ACL-07) | Entire channel sub-protocol is unhandled today. |
| inc.channel.emit | not routed explicitly (DRIFT-RT-09) | no channel handler | not gated (DRIFT-ACL-07) | Sender exclusion + point-to-point delivery needed. |
| theme.get | missing (DRIFT-RT-05) | missing (DRIFT-SVC-06) | present (resolve.ts:124-125, null/null pass-through) | ACL allows; runtime/service missing. |
| theme.changed | missing (DRIFT-RT-05) | missing (DRIFT-SVC-06) | present (same case 'theme') | Shell-initiated push path requires DRIFT-SHELL-05. |
Summary
- Total DRIFT rows across sections 1-5: 33 (ACL 9, RT 10, SHELL 8, SVC 8, CORE 5). Adds comfortably beyond the minimum (25) and per-namespace minimums (7/9/5/7/4).
- Target Phase distribution: Phase 11: 3 rows (DRIFT-CORE-03, -04, -05) — ✅ resolved. Phase 12: 26 rows — ✅ resolved. Phase 13: 3 rows (DRIFT-RT-05, DRIFT-SHELL-05, DRIFT-SVC-06) — ✅ resolved. Phase 14: 2 rows (DRIFT-CORE-01, -02) — remain.
- 8-domain matrix rows: 26 entries, covering every one of the eight canonical domains (identity, relay, keys, media, notify, storage, inc, theme).
- Canonical-spec delta coverage:
window.nostrprohibition — DRIFT-SHELL-01 (explicit);perm:namespace — DRIFT-SHELL-02 (explicit); shell-mediated signing viarelay.publishEncrypted— DRIFT-RT-07, DRIFT-RT-08, DRIFT-ACL-06, DRIFT-SHELL-03, DRIFT-SVC-01, DRIFT-SVC-08. - Traceability guarantee: Every drift row cites either a
packages/<pkg>/src/<file>.ts[:line]path (existing code) or an explicit "no file exists yet" absence (new code to be created). Phase 12/13/14 planners can enumerate their work by filtering the Target Phase column.
Phase 12 status (recorded 2026-04-17)
26 of 26 Phase 12 rows resolved. Resolution ownership:
| Plan | Scope | Rows closed |
|---|---|---|
| 12-01 | Shell conformance — window.nostr removal + capability list scrub | DRIFT-SHELL-01, DRIFT-SHELL-03, DRIFT-SHELL-04 |
| 12-02 | shell.supports() perm:-namespace contract | DRIFT-SHELL-02 |
| 12-03 | Identity migration + signer-domain removal + signer-service deletion | DRIFT-RT-01, DRIFT-RT-06, DRIFT-RT-07, DRIFT-RT-10, DRIFT-SVC-01, DRIFT-SVC-02, DRIFT-SVC-07 |
| 12-04 | inc channel sub-protocol | DRIFT-RT-09 |
| 12-05 | keys service + runtime dispatch | DRIFT-RT-02, DRIFT-SVC-03 |
| 12-06 | media service + runtime dispatch | DRIFT-RT-03, DRIFT-SVC-04 |
| 12-07 | notify service + runtime dispatch | DRIFT-RT-04, DRIFT-SVC-05 |
| 12-08 | relay.publishEncrypted routing | DRIFT-RT-08, DRIFT-SVC-08 |
| 12-09 | storage narrowing to 4 canonical actions | (shared with 12-10 ACL narrowing) |
| 12-10 | ACL consolidation + audit closure | DRIFT-ACL-01 through DRIFT-ACL-09 |
| 12-11 | Shell per-domain proxies + keys-forwarder + barrel cleanup | DRIFT-SHELL-06, DRIFT-SHELL-07, DRIFT-SHELL-08 |
Remaining rows:
- Phase 14 (2 rows): DRIFT-CORE-01, DRIFT-CORE-02 — dispatch refactor to
createDispatch()/registerNap().
Phase 13 status (recorded 2026-04-17)
3 of 3 Phase 13 rows resolved. Resolution ownership:
| Plan | Scope | Rows closed |
|---|---|---|
| 13-01 | Runtime case 'theme': dispatch + @kehto/services theme-service with publishTheme broadcast handle | DRIFT-RT-05, DRIFT-SVC-06 |
| 13-02 | Shell adapter bridge.publishTheme(theme) host-facing broadcast API | DRIFT-SHELL-05 |
Phase 12 closure recorded 2026-04-17. Phase 13 closure recorded 2026-04-17. Phase 14 remains.