Skip to content

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 under packages/, and the typedoc-generated reference at docs/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:

  1. The canonical NIP-5D spec text (specs/NIP-5D.md).
  2. 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:

  1. window.nostr MUST NOT be provided to napplet iframes (specs/NIP-5D.md line 44 + Security §6). Reverses v1.1 SH-I02 — any shell-side injection must be removed.
  2. shell.supports() uses namespaced capability stringsperm:<permission> for sandbox permissions (e.g., perm:popups), bare or nap: for NAP capabilities (specs/NIP-5D.md lines 81-94).
  3. Signing and encryption are shell-mediated via relay.publish / relay.publishEncrypted — no napplet-side signer.* messages exist in canonical NIP-5D. The former signer domain is dissolved into read-only identity (for public info) plus shell-internal signing inside relay publishes.

Target Phase Mapping

Target PhaseScope
11Peer-dep prerequisites (@napplet/core bump, eight nap peer deps declared)
12Shell conformance + seven non-theme nap coverage + ACL mapping for all 8 domains + signer-domain removal
13Theme NAP end-to-end implementation (runtime route, reference service, shell adapter)
14Replace hand-rolled dispatch switch with @napplet/core createDispatch() / registerNap()

ID Legend

NamespaceScope
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-NNCross-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.

IDDrift ItemCurrent StateSpec/Package RequirementTarget PhaseRemediation Note
DRIFT-ACL-01resolveCapabilitiesNap has no case 'identity' — identity.* falls through to default-nullpackages/acl/src/resolve.ts:105 switch — only relay/signer/storage/inc/theme casesNAP-03, NAP-1012Add case 'identity': with per-action mapping for the 9 identity requests. Resolved in Plan 12-10.
DRIFT-ACL-02resolveCapabilitiesNap has no case 'keys'packages/acl/src/resolve.ts:105 — no keys branch (no file exists)NAP-05, NAP-1012Add case 'keys': covering forward, registerAction, unregisterAction. Resolved in Plan 12-10.
DRIFT-ACL-03resolveCapabilitiesNap has no case 'media'packages/acl/src/resolve.ts:105 — no media branchNAP-06, NAP-1012Add case 'media': covering session.*, state, capabilities. Resolved in Plan 12-10.
DRIFT-ACL-04resolveCapabilitiesNap has no case 'notify'packages/acl/src/resolve.ts:105 — no notify branchNAP-07, NAP-1012Add case 'notify': with default-prompt semantics; user-visible surface. Resolved in Plan 12-10.
DRIFT-ACL-05resolveCapabilitiesNap still has case 'signer' at line 109packages/acl/src/resolve.ts:109-115 — resolves getPublicKey/getRelays/nip04/nip44/signEventNAP-02, NAP-1012Remove case 'signer': entirely; migrate getPublicKey/getRelays references to identity; drop nip04/nip44/signEvent. Resolved in Plan 12-10.
DRIFT-ACL-06relay ACL does not distinguish relay.publishEncrypted from relay.publishpackages/acl/src/resolve.ts:105-108 — only action==='publish' branch for relay:writeNAP-08, SH-C0312Split relay branch: publishEncrypted requires a stricter relay:write + sign:nip44 composite gate. Resolved in Plan 12-10.
DRIFT-ACL-07inc 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 branchNAP-04, NAP-1012Extend 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-08storage ACL lacks coverage for keys.result discrimination and the eliminated storage.clear capabilitypackages/acl/src/resolve.ts:116-119get|keys → state:read / default → state:write; clear still in default bucket though no napplet message existsNAP-09, NAP-1012Narrow storage branch to the 4 canonical requests (get, set, remove, keys); drop clear support. Resolved in Plan 12-10.
DRIFT-ACL-09Identity 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-1012Within new identity case, map getPublicKey/getRelays → null (shell-public) and getProfile/getFollows/getList/getZaps/getMutes/getBlocked/getBadgesrelay: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.

IDDrift ItemCurrent StateSpec/Package RequirementTarget PhaseRemediation Note
DRIFT-RT-01Dispatch switch has no case 'identity'packages/runtime/src/runtime.ts:744-749 — cases are relay/signer/storage/incNAP-0312Add case 'identity': return handleIdentityMessage(windowId, envelope); and wire to a new identity handler. Resolved in Plan 12-03.
DRIFT-RT-02Dispatch switch has no case 'keys'packages/runtime/src/runtime.ts:744-749NAP-0512Add 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-03Dispatch switch has no case 'media'packages/runtime/src/runtime.ts:744-749NAP-0612Add case 'media': routed to @kehto/services media-service. Resolved in Plan 12-06.
DRIFT-RT-04Dispatch switch has no case 'notify'packages/runtime/src/runtime.ts:744-749NAP-0712Add case 'notify': routed to @kehto/services notify-service. Resolved in Plan 12-07.
DRIFT-RT-05Dispatch switch has no case 'theme'packages/runtime/src/runtime.ts:744-749TH-0113Add 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-06Dispatch switch has case 'signer' at line 746 — canonical spec has no signer domainpackages/runtime/src/runtime.ts:746 — routes to handleSignerMessageNAP-0212Remove 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-07Shell-proxy branch exposes signer.* as a postMessage-addressable surfacepackages/runtime/src/runtime.ts:593-673handleSignerMessage() handles signer.signEvent, signer.nip04.encrypt/decrypt, signer.nip44.encrypt/decryptSH-C0312Delete handleSignerMessage(); encryption primitives become private helpers called by handleRelayMessage for relay.publishEncrypted. Resolved in Plan 12-03.
DRIFT-RT-08Relay handler does not route relay.publishEncrypted separately from relay.publishpackages/runtime/src/runtime.ts:547-576case 'publish': only; no case 'publishEncrypted':NAP-0812Add 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-09inc.channel.* messages have no explicit routing — inc handler pre-dates the channel sub-protocolpackages/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: breakNAP-0412Extend 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-10Signer 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 fallbackNAP-0212Remove 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.

IDDrift ItemCurrent StateSpec/Package RequirementTarget PhaseRemediation Note
DRIFT-SHELL-01window.nostr is injected into napplet iframespackages/shell/src/shell-init.ts:103window.nostr = { getPublicKey, signEvent, getRelays, nip04, nip44 }; bootstrap emitted by generateNostrBootstrap() (lines 50-131)SH-C0112Delete 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-02shell.supports() capability list uses bare permission names instead of perm:<permission> namespacepackages/shell/src/shell-init.ts:42-47buildShellCapabilities() returns { naps: [...], sandbox: [] }; callers pass bare namesSH-C0212Route 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-03Shell does not mediate signing/encryption — napplets reach signing primitives via postMessagepackages/shell/src/shell-init.ts:103-128 — bootstrap exposes signEvent/nip04/nip44; ShellBridge at packages/shell/src/shell-bridge.ts:165-170 advertises signer capabilitySH-C0312Move 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-04Shell-init declares 'signer' as a NAP capabilitypackages/shell/src/shell-init.ts:43const naps: string[] = ['signer', 'storage', 'inc'];SH-C03, NAP-0212Replace 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-05Shell has no theme adapter API for broadcasting theme changes to nappletspackages/shell/src/ — no theme-proxy.ts; shell-bridge.ts has no theme.set / theme.changed pathTH-0313Add 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-06Shell lacks a keys-forwarding adapter path — napplets receive no keyboard events from the hostpackages/shell/src/ — no keys proxy; hotkeyHooks.executeHotkeyFromForward exists for the reverse direction only (napplet→shell)NAP-0512Add 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-07Shell re-exports SignerProxy/signer bindings via barrel index.tspackages/shell/src/index.ts:15-45 — re-exports types and the signer-related NIP-07 facade from shell-initSH-C03, NAP-0212Remove 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-08Shell 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 filesNAP-03, NAP-05, NAP-06, NAP-07, TH-0312Create 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.

IDDrift ItemCurrent StateSpec/Package RequirementTarget PhaseRemediation Note
DRIFT-SVC-01signer-service.ts implements the removed signer.* domainpackages/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:45NAP-02, NAP-03, SH-C0312Migrate 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-02No identity-service.ts — identity.* (9 requests) has no reference handlerpackages/services/src/ — no file exists yetNAP-0312Create 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-03No keys-service.ts — keys.* (6 messages) has no reference handlerpackages/services/src/ — no file exists yetNAP-0512Create 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-04No media-service.ts — media.* (8 messages) has no reference handlerpackages/services/src/audio-service.ts exists but is wired to inc.emit on audio:* topics (not under the canonical media domain)NAP-0612Create 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-05No notify-service.ts — notify.* (11 messages) has no reference handlerpackages/services/src/notification-service.ts exists but operates on inc.emit / legacy API (not under canonical notify domain)NAP-0712Create (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-06No theme-service.ts — theme.* (3 messages) has no reference handlerpackages/services/src/ — no file exists yetTH-0213Create theme-service.ts implementing theme.get/.result and shell-initiated theme.changed broadcast. Status: ✅ Resolved in Phase 13 (Plan 13-01).
DRIFT-SVC-07packages/services/src/signer-service.test.ts exercises the removed signer.* surfacepackages/services/src/signer-service.test.ts — full co-located test suite against createSignerServiceDEPS-0312Migrate 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-08No relay-service that exposes a canonical relay.publishEncrypted pathpackages/services/src/relay-pool-service.ts — handles relay.subscribe, relay.close, relay.publish only; no publishEncryptedNAP-0812Extend 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.

IDDrift ItemCurrent StateSpec/Package RequirementTarget PhaseRemediation Note
DRIFT-CORE-01Runtime 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-0314After all eight domain handlers land (Phase 12/13), replace the switch with dispatch(envelope) and remove the per-domain case statements.
DRIFT-CORE-02No runtime entry-point calls registerNap() for any of the 8 domains — handlers are attached via switch branches insteadpackages/runtime/src/runtime.ts:444-720 — each domain handler is a bespoke function (handleRelayMessage, handleSignerMessage, handleStorageMessage, handleIncMessage); no registerNap call anywhere in the packageDISPATCH-0214At createRuntime() startup, call registerNap('<domain>', handler) for each of the 8 domains; feed all handlers into createDispatch().
DRIFT-CORE-03kehto packages peer-depend on @napplet/core at >=0.1.0 — must bump to ^0.2.0packages/{acl,runtime,shell,services}/package.json — each declares "@napplet/core": ">=0.1.0" under peerDependenciesDEPS-0111Bump peer range to ^0.2.0 across all four packages; add changesets; verify workspace resolution still works.
DRIFT-CORE-04None of the eight @napplet/nap-* packages appear in any @kehto/* package.json peerDependencies blockpackages/{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 entriesNAP-0111Add all eight nap peer-deps uniformly to each kehto package; types-only consumption (no runtime import footprint expected).
DRIFT-CORE-05Hand-copied NAP message types live inside kehto; no imports from @napplet/nap-* exist yetpackages/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-* declarationsNAP-0211After 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.MessageRuntime DispatchService HandlerACL MappingNotes
identity.getPublicKeymissing (DRIFT-RT-01)missing (DRIFT-SVC-02)missing (DRIFT-ACL-01, DRIFT-ACL-09)Read-only; currently served via legacy signer.getPublicKey path.
identity.getRelaysmissing (DRIFT-RT-01)missing (DRIFT-SVC-02)missing (DRIFT-ACL-01, DRIFT-ACL-09)Currently served via legacy signer.getRelays path.
identity.getProfilemissing (DRIFT-RT-01)missing (DRIFT-SVC-02)missing (DRIFT-ACL-01, DRIFT-ACL-09)No path at all today.
identity.getFollowsmissing (DRIFT-RT-01)missing (DRIFT-SVC-02)missing (DRIFT-ACL-01, DRIFT-ACL-09)No path.
identity.getBadgesmissing (DRIFT-RT-01)missing (DRIFT-SVC-02)missing (DRIFT-ACL-01, DRIFT-ACL-09)No path.
relay.subscribepresent (packages/runtime/src/runtime.ts:452, case 'subscribe')relay-pool-servicepresent (resolve.ts:105-108, relay:read)Spec-conformant today.
relay.publishpresent (packages/runtime/src/runtime.ts:547, case 'publish')relay-pool-servicepresent (relay:write + relay:read)Accepts napplet-signed events; must migrate to shell-mediated signing (DRIFT-RT-07, DRIFT-SHELL-03).
relay.publishEncryptednot 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.querypresent (packages/runtime/src/runtime.ts:578, case 'query')cache-servicepresent (relay:read)Spec-conformant.
keys.forwardmissing (DRIFT-RT-02)missing (DRIFT-SVC-03)missing (DRIFT-ACL-02)Fire-and-forget; high-frequency path.
keys.registerActionmissing (DRIFT-RT-02)missing (DRIFT-SVC-03)missing (DRIFT-ACL-02)Needs result envelope.
keys.actionmissing (DRIFT-RT-02)missing (DRIFT-SVC-03)missing (DRIFT-ACL-02)Shell-initiated push path.
media.session.createmissing (DRIFT-RT-03)missing (DRIFT-SVC-04)missing (DRIFT-ACL-03)Needs result envelope.
media.commandmissing (DRIFT-RT-03)missing (DRIFT-SVC-04)missing (DRIFT-ACL-03)Shell-initiated push path.
media.statemissing (DRIFT-RT-03)missing (DRIFT-SVC-04)missing (DRIFT-ACL-03)High-frequency state report.
notify.sendmissing (DRIFT-RT-04)missing (DRIFT-SVC-05)missing (DRIFT-ACL-04)User-visible surface; prompt default.
notify.permission.requestmissing (DRIFT-RT-04)missing (DRIFT-SVC-05)missing (DRIFT-ACL-04)Asynchronous permission flow.
notify.clickedmissing (DRIFT-RT-04)missing (DRIFT-SVC-05)missing (DRIFT-ACL-04)Shell-initiated push path.
storage.getpresent (packages/runtime/src/runtime.ts:675-677, handleStorageMessage)state-handler (storage-nap)present (state:read)Spec-conformant.
storage.setpresent (packages/runtime/src/runtime.ts:675-677)state-handlerpresent (state:write)Spec-conformant.
storage.keyspresent (packages/runtime/src/runtime.ts:675-677)state-handlerpresent (state:read)Spec-conformant.
inc.emitpresent (packages/runtime/src/runtime.ts:685, case 'emit')inline inc handlerpresent (resolve.ts:121-122, relay:write + relay:read)Spec-conformant for topic emit.
inc.subscribepresent (packages/runtime/src/runtime.ts:700, case 'subscribe')inline inc handlerpresent (relay:read)Spec-conformant.
inc.channel.opennot routed explicitly (DRIFT-RT-09)no channel handlernot gated for channel sub-protocol (DRIFT-ACL-07)Entire channel sub-protocol is unhandled today.
inc.channel.emitnot routed explicitly (DRIFT-RT-09)no channel handlernot gated (DRIFT-ACL-07)Sender exclusion + point-to-point delivery needed.
theme.getmissing (DRIFT-RT-05)missing (DRIFT-SVC-06)present (resolve.ts:124-125, null/null pass-through)ACL allows; runtime/service missing.
theme.changedmissing (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.nostr prohibition — DRIFT-SHELL-01 (explicit); perm: namespace — DRIFT-SHELL-02 (explicit); shell-mediated signing via relay.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:

PlanScopeRows closed
12-01Shell conformance — window.nostr removal + capability list scrubDRIFT-SHELL-01, DRIFT-SHELL-03, DRIFT-SHELL-04
12-02shell.supports() perm:-namespace contractDRIFT-SHELL-02
12-03Identity migration + signer-domain removal + signer-service deletionDRIFT-RT-01, DRIFT-RT-06, DRIFT-RT-07, DRIFT-RT-10, DRIFT-SVC-01, DRIFT-SVC-02, DRIFT-SVC-07
12-04inc channel sub-protocolDRIFT-RT-09
12-05keys service + runtime dispatchDRIFT-RT-02, DRIFT-SVC-03
12-06media service + runtime dispatchDRIFT-RT-03, DRIFT-SVC-04
12-07notify service + runtime dispatchDRIFT-RT-04, DRIFT-SVC-05
12-08relay.publishEncrypted routingDRIFT-RT-08, DRIFT-SVC-08
12-09storage narrowing to 4 canonical actions(shared with 12-10 ACL narrowing)
12-10ACL consolidation + audit closureDRIFT-ACL-01 through DRIFT-ACL-09
12-11Shell per-domain proxies + keys-forwarder + barrel cleanupDRIFT-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:

PlanScopeRows closed
13-01Runtime case 'theme': dispatch + @kehto/services theme-service with publishTheme broadcast handleDRIFT-RT-05, DRIFT-SVC-06
13-02Shell adapter bridge.publishTheme(theme) host-facing broadcast APIDRIFT-SHELL-05

Phase 12 closure recorded 2026-04-17. Phase 13 closure recorded 2026-04-17. Phase 14 remains.