Initial Commit
This commit is contained in:
103
openspec/changes/native-messaging/proposal.md
Normal file
103
openspec/changes/native-messaging/proposal.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# OpenSpec Change: native-messaging
|
||||
|
||||
- **Change ID**: native-messaging
|
||||
- **Package**: `@suaveplan/native-messaging`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-04-29
|
||||
|
||||
## Summary
|
||||
|
||||
Create `@suaveplan/native-messaging`, a typed, encrypted transport layer between browser extensions and the desktop vault application. The package abstracts Chrome stdio, Firefox stdio, and Safari `NSExtensionContext` behind a single interface, using X25519 key exchange for handshake and ChaCha20-Poly1305 for per-frame encryption.
|
||||
|
||||
## Why
|
||||
|
||||
The Vaulted password-manager architecture requires browser extensions to communicate with the desktop app without exposing vault keys inside the extension runtime. No existing Suaveplan package provides a cross-browser native-messaging abstraction with built-in encryption. External libraries either lack typed message framing or bundle their own crypto primitives, which violates the monorepo's policy of reusing `@suaveplan/crypto-curve` and `@suaveplan/crypto-cipher`.
|
||||
|
||||
## What changes
|
||||
|
||||
- ADDED: `NativeMessagingClient` — platform-agnostic encrypted channel client used by browser extension background scripts.
|
||||
- ADDED: `NativeMessagingHost` — desktop-side encrypted channel host accepting connections from extensions.
|
||||
- ADDED: `Handshake` — X25519 ephemeral key exchange producing a shared secret and session keys.
|
||||
- ADDED: `FrameEncoder` / `FrameDecoder` — length-prefixed, ChaCha20-Poly1305-encrypted message framing with AAD.
|
||||
- ADDED: `ChromeTransport`, `FirefoxTransport`, `SafariTransport` — platform-specific transport adapters behind a common `Transport` interface.
|
||||
- ADDED: `TypedMessage<T>` — discriminated-union envelope for request/response/error messages with Zod schema validation at boundaries.
|
||||
- ADDED: `ReconnectPolicy` — configurable exponential-backoff reconnection with jitter and max-retry caps.
|
||||
- ADDED: `NativeMessagingError` — typed error hierarchy (`HandshakeFailed`, `FrameCorrupted`, `TransportClosed`, `Timeout`) with error codes.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Mobile Capacitor bridge communication — mobile uses in-process bridging, not native messaging.
|
||||
- Biometric unlock flow integration — `@suaveplan/biometrics` will orchestrate that layer.
|
||||
- Vault item CRUD logic — belongs in `@suaveplan/vault`.
|
||||
- WebSocket or WebRTC fallbacks — separate transport packages already exist in the monorepo.
|
||||
- Extension content-script injection — handled by `@suaveplan/form-detection` and extension build pipelines.
|
||||
|
||||
## Gap analysis: sub-standard → enterprise
|
||||
|
||||
Most browser-extension ↔ native-application bridges ship plaintext JSON over stdio with no authentication, no replay protection, and no message-level typing. This leaves them vulnerable to man-in-the-browser attacks, host-process impersonation, and trivial message injection. Enterprise deployments require encrypted, authenticated, typed, and recoverable channels. This proposal closes those gaps by mandating X25519 key exchange, ChaCha20-Poly1305 per-frame AEAD, Zod-validated discriminated-union messages, and configurable reconnection with session isolation.
|
||||
|
||||
## Security model
|
||||
|
||||
- **Threat model**: Malicious extension impersonation, compromised host process, man-in-the-browser, replay attacks, and ephemeral-key exfiltration.
|
||||
- **Encryption boundaries**: X25519 ephemeral key exchange for handshake; ChaCha20-Poly1305 for every frame with AAD binding the message type and sequence number.
|
||||
- **Input validation**: Zod schemas at all public boundaries (`TypedMessage`, `HandshakePayload`, `FrameHeader`). Any validation failure terminates the session.
|
||||
- **Side-channel resistance**: Constant-time shared-secret comparison during handshake; nonce generation via monorepo CSPRNG (`@suaveplan/crypto-curve`).
|
||||
- **Key zeroization**: Session keys and ephemeral private keys are explicitly overwritten with zero-filled buffers immediately after handshake completion or transport disconnect.
|
||||
- **Privacy guarantees**: No vault plaintext, no passwords, and no biometric data traverse the channel or appear in logs. Logs contain only error codes, connection IDs, and frame sequence numbers.
|
||||
|
||||
## Performance SLOs
|
||||
|
||||
- **Handshake latency**: < 50 ms p99 on target hardware.
|
||||
- **Per-frame crypto**: < 1 ms for encryption or decryption p99.
|
||||
- **Throughput**: ≥ 1 000 messages/sec per active channel.
|
||||
- **Memory**: < 2 MB per active connection (keys, buffers, pending queue).
|
||||
- **Bundle size**: client core < 10 KB gzipped; host core < 15 KB gzipped; tree-shakeable by transport adapter.
|
||||
|
||||
## Observability plan
|
||||
|
||||
- **OTel spans**: `native_messaging.handshake`, `native_messaging.frame.encode`, `native_messaging.frame.decode`, `native_messaging.transport.send`, `native_messaging.transport.receive`.
|
||||
- **Metrics**: active connection gauge, reconnection rate counter, frame error rate counter, handshake duration histogram.
|
||||
- **Structured logging**: JSON-only, with `trace_id`, `connection_id`, and `error_code`; never includes payloads or keys.
|
||||
- **Error telemetry**: automatic capture of `FrameCorrupted`, `HandshakeFailed`, and `TransportClosed` with stack-traces scrubbed of PII.
|
||||
- **Health checks**: `NativeMessagingHost` exposes an internal `health()` returning active connection count, last error timestamp, and handshake success rate over the last 60 s.
|
||||
|
||||
## Dependency audit
|
||||
|
||||
- `@suaveplan/crypto-curve` — X25519 primitives. Justified: monorepo policy, audited, no external WebCrypto leakage.
|
||||
- `@suaveplan/crypto-cipher` — ChaCha20-Poly1305. Justified: monorepo policy, audited, constant-time where applicable.
|
||||
- `zod` — runtime schema validation. Justified: existing monorepo standard, MIT license, fully tree-shakeable.
|
||||
- **Zero additional runtime dependencies**. Supply-chain risk is minimized via lockfile pinning and provenance manifest generation. All listed deps are tree-shakeable and carry permissive licenses.
|
||||
|
||||
## Operational readiness
|
||||
|
||||
- **Graceful degradation**: If the handshake fails or the transport closes unexpectedly, the client surfaces a typed `HandshakeFailed` or `TransportClosed` error. There is no silent fallback to plaintext.
|
||||
- **Resource limits**: Max 64 concurrent connections per host; max frame size 1 MB; max pending send queue depth 256 messages.
|
||||
- **Error recovery and reconnection**: `ReconnectPolicy` provides exponential backoff with full jitter, capped retry count, and per-session isolation so a poisoned session cannot corrupt a new one.
|
||||
- **Backpressure**: Bounded send buffer; exceeding the bound throws `BackpressureExceeded` rather than allowing unbounded memory growth.
|
||||
- **Version compatibility**: Major-version negotiation during handshake; incompatible versions terminate immediately with a typed `VersionMismatch` error.
|
||||
|
||||
## Verification approach
|
||||
|
||||
- `src/handshake/handshake.test.ts` — covers X25519 key generation, shared-secret derivation, and session-key split.
|
||||
- `src/frame/frame.test.ts` — covers encryption, decryption, AAD integrity, tampered-frame rejection, and nonce sequencing.
|
||||
- `src/transport/transport.test.ts` — covers Chrome/Firefox/Safari adapter lifecycle, `send`, `receive`, `close`, and event emission.
|
||||
- `src/client/client.test.ts` — covers typed request/response round-trips, timeout handling, and error propagation.
|
||||
- `src/host/host.test.ts` — covers host accept/reject, concurrent connections, and session isolation.
|
||||
- `src/reconnect/reconnect.test.ts` — covers backoff timing, jitter distribution, and max-retry exhaustion.
|
||||
- `src/error/error.test.ts` — covers error code uniqueness, `instanceof` branches, and serialization round-trip.
|
||||
- `src/spec-conformance.test.ts` — pins no-internal-CSPRNG invariant, subpath-export count, and sibling-doc presence.
|
||||
- `tests/dep-graph.test.ts` — pins dependency invariants (only `@suaveplan/crypto-curve` and `@suaveplan/crypto-cipher`).
|
||||
- **100 % coverage target** for all non-test source files.
|
||||
- **Property-based tests** (`fast-check`): frame round-trip invariants, nonce uniqueness across 10 000 iterations, backoff monotonicity and jitter bounds.
|
||||
- **Load / stress criteria**: 10 000 concurrent handshakes without memory growth; 1 000 000 frame round-trips with < 0.01 % corruption; 24-hour churn test with no handle leaks.
|
||||
- **Security audit checklist**: key zeroization verified in heap dumps; replay-attack simulation rejected by AAD; tampered-frame rejection rate 100 %; CSPRNG bypass attempts blocked.
|
||||
|
||||
## Migration impact
|
||||
|
||||
None — additive only.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
No dependents (verified by `bunx turbo ls --filter=...@suaveplan/native-messaging`).
|
||||
172
openspec/changes/native-messaging/specs/native-messaging/spec.md
Normal file
172
openspec/changes/native-messaging/specs/native-messaging/spec.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Capability Spec Delta: native-messaging (native-messaging)
|
||||
|
||||
**Package**: `@suaveplan/native-messaging`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. (See `openspec/AGENTS.md` Section 13.)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: X25519 Handshake Protocol
|
||||
|
||||
The package SHALL perform an ephemeral X25519 key exchange between the browser extension (client) and the desktop application (host) to derive a shared secret. The handshake SHALL produce two distinct session keys — one for client-to-host frames and one for host-to-client frames — via HKDF-SHA256. The handshake SHALL complete in a single round-trip and SHALL reject connections if the peer's public key is not a valid X25519 point.
|
||||
|
||||
#### Scenario: Successful handshake derives distinct session keys
|
||||
|
||||
- **WHEN** the client generates an ephemeral X25519 keypair and sends its public key to the host
|
||||
- **THEN** the host generates its own ephemeral keypair, computes the shared secret, and derives two session keys that are not equal to each other
|
||||
|
||||
#### Scenario: Invalid peer public key aborts handshake
|
||||
|
||||
- **WHEN** the client receives a peer public key that is all-zeroes or not on the X25519 curve
|
||||
- **THEN** the handshake throws `HandshakeFailed` with error code `NATIVE_MSG_HANDSHAKE_INVALID_KEY`
|
||||
|
||||
#### Scenario: Handshake is replay-resistant
|
||||
|
||||
- **WHEN** the same handshake payload is sent a second time with identical ephemeral keys
|
||||
- **THEN** the derived session keys are identical (deterministic), but the host MAY reject duplicate ephemeral public keys at the application layer
|
||||
|
||||
**Verified by:** `src/handshake/handshake.test.ts::"derives distinct client-to-host and host-to-client session keys"` AND `src/handshake/handshake.test.ts::"throws HandshakeFailed for all-zeroes peer public key"` AND `src/handshake/handshake.spec-conformance.test.ts::"replay of identical handshake yields identical keys"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Injectable RNG
|
||||
|
||||
The package SHALL accept an optional `Rng` instance in the handshake options (or transport config / client constructor) so that tests and high-assurance callers can substitute a deterministic or audited random source. When provided, the `Rng` SHALL be used for all ephemeral X25519 key generation and handshake nonce generation. When omitted, the package SHALL fall back to the monorepo default CSPRNG.
|
||||
|
||||
#### Scenario: Handshake uses injected RNG for ephemeral keys and nonces
|
||||
|
||||
- **WHEN** a custom `Rng` is passed in the handshake options
|
||||
- **THEN** all ephemeral X25519 keypairs and handshake nonces are generated from the injected `Rng`
|
||||
|
||||
#### Scenario: Handshake falls back to default CSPRNG when no RNG provided
|
||||
|
||||
- **WHEN** no `rng` option is supplied
|
||||
- **THEN** ephemeral key generation and nonce generation use the monorepo default CSPRNG (`@suaveplan/crypto-curve`)
|
||||
|
||||
**Verified by:** `src/handshake/handshake.test.ts::"uses injected Rng for ephemeral keys and nonces"` AND `src/handshake/handshake.test.ts::"falls back to default CSPRNG when rng is omitted"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ChaCha20-Poly1305 Frame Encryption
|
||||
|
||||
The package SHALL encrypt every native-messaging payload as a discrete frame using ChaCha20-Poly1305. Each frame SHALL carry a 12-byte nonce, ciphertext, and 16-byte Poly1305 tag. The nonce SHALL be a monotonically incrementing counter starting from zero for each direction per session. Frames SHALL include the message type as Additional Authenticated Data (AAD) so that a frame cannot be repurposed across message kinds.
|
||||
|
||||
#### Scenario: Frame encrypts and decrypts with integrity
|
||||
|
||||
- **WHEN** a plaintext message is framed with a known message type and encrypted under a session key
|
||||
- **THEN** the decrypted plaintext and message type are identical to the originals
|
||||
|
||||
#### Scenario: Tampered frame is rejected
|
||||
|
||||
- **WHEN** a single bit in the ciphertext or tag is flipped before decryption
|
||||
- **THEN** decryption throws `FrameCorrupted` with error code `NATIVE_MSG_FRAME_TAG_MISMATCH`
|
||||
|
||||
#### Scenario: Wrong AAD (message type) is rejected
|
||||
|
||||
- **WHEN** a frame is decrypted with a different message type supplied as AAD
|
||||
- **THEN** decryption throws `FrameCorrupted` with error code `NATIVE_MSG_FRAME_AAD_MISMATCH`
|
||||
|
||||
#### Scenario: Nonce reuse in the same direction is rejected
|
||||
|
||||
- **WHEN** a frame is encrypted with a nonce that has already been used in the current session direction
|
||||
- **THEN** the encoder throws `FrameCorrupted` with error code `NATIVE_MSG_FRAME_NONCE_REUSE`
|
||||
|
||||
**Verified by:** `src/frame/frame.test.ts::"round-trips plaintext and message type"` AND `src/frame/frame.test.ts::"rejects tampered ciphertext with FrameCorrupted"` AND `src/frame/frame.test.ts::"rejects wrong AAD during decryption"` AND `src/frame/frame.spec-conformance.test.ts::"throws on nonce reuse in same direction"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Transport Abstraction
|
||||
|
||||
The package SHALL provide a common `Transport` interface that hides platform specifics. Concrete adapters SHALL exist for Chrome `runtime.connectNative` stdio, Firefox `runtime.connectNative` stdio, and Safari `NSExtensionContext` in-process messaging. The interface SHALL expose `send(frame: Uint8Array): Promise<void>`, `receive(): Promise<Uint8Array>`, `close(): Promise<void>`, and an `onClose` event emitter. Consumers SHALL not need to branch on `navigator.userAgent` outside the adapter factory.
|
||||
|
||||
#### Scenario: Chrome adapter sends length-prefixed frames
|
||||
|
||||
- **WHEN** the Chrome transport adapter sends a frame
|
||||
- **THEN** the frame is prefixed with a 32-bit little-endian length header matching native-messaging host protocol conventions
|
||||
|
||||
#### Scenario: Firefox adapter sends length-prefixed frames
|
||||
|
||||
- **WHEN** the Firefox transport adapter sends a frame
|
||||
- **THEN** the frame is prefixed with a 32-bit little-endian length header identical to Chrome behavior
|
||||
|
||||
#### Scenario: Safari adapter marshals through NSExtensionContext
|
||||
|
||||
- **WHEN** the Safari transport adapter sends a frame
|
||||
- **THEN** the frame is delivered via the Capacitor/Electrobun bridge or `NSExtensionContext` without a length prefix
|
||||
|
||||
#### Scenario: Adapter factory selects correct transport at runtime
|
||||
|
||||
- **WHEN** `createTransport(platform)` is called with `'chrome'`, `'firefox'`, or `'safari'`
|
||||
- **THEN** the returned instance is `ChromeTransport`, `FirefoxTransport`, or `SafariTransport` respectively
|
||||
|
||||
**Verified by:** `src/transport/transport.test.ts::"Chrome adapter prefixes 32-bit LE length"` AND `src/transport/transport.test.ts::"Firefox adapter prefixes 32-bit LE length"` AND `src/transport/transport.test.ts::"Safari adapter bypasses length prefix"` AND `src/transport/transport.test.ts::"factory returns correct adapter for each platform"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Error Recovery and Reconnection
|
||||
|
||||
The package SHALL expose a `ReconnectPolicy` with configurable exponential backoff, jitter, and maximum retry attempts. When the underlying transport closes unexpectedly, the client SHALL attempt reconnection according to the policy. The client SHALL preserve the session state (including derived keys) only when the transport layer closes cleanly with a `TEMPORARY` close code; on an `UNRECOVERABLE` close code, the client SHALL destroy session keys and require a fresh handshake.
|
||||
|
||||
#### Scenario: Exponential backoff with jitter
|
||||
|
||||
- **WHEN** the transport fails three times in a row with a temporary error
|
||||
- **THEN** the delays between attempts follow `baseDelay * 2^attempt + jitter` and never exceed `maxDelay`
|
||||
|
||||
#### Scenario: Max retries exhausts and throws
|
||||
|
||||
- **WHEN** the transport fails more times than `maxRetries`
|
||||
- **THEN** the client throws `TransportClosed` with error code `NATIVE_MSG_TRANSPORT_MAX_RETRIES` and emits `onFatal`
|
||||
|
||||
#### Scenario: Unrecoverable close destroys session keys
|
||||
|
||||
- **WHEN** the transport closes with close code `UNRECOVERABLE`
|
||||
- **THEN** all session key material is zeroized and the next `connect()` performs a full handshake
|
||||
|
||||
#### Scenario: Clean close preserves session for reuse
|
||||
|
||||
- **WHEN** the transport closes with close code `TEMPORARY`
|
||||
- **THEN** session keys remain in memory and the next `connect()` skips the handshake, resuming encryption with the next nonce counter value
|
||||
|
||||
**Verified by:** `src/reconnect/reconnect.test.ts::"backoff delays follow exponential growth with jitter"` AND `src/reconnect/reconnect.test.ts::"throws TransportClosed after maxRetries"` AND `src/client/client.test.ts::"zeroizes session keys on UNRECOVERABLE close"` AND `src/client/client.test.ts::"resumes session on TEMPORARY close without re-handshake"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Typed Message Interface
|
||||
|
||||
The package SHALL define a `TypedMessage<T>` discriminated-union envelope with `kind`, `payload`, and `id` fields. Request/response matching SHALL use the `id` field. Every inbound message SHALL be validated against a Zod schema at the transport boundary before reaching business logic. Unknown `kind` values SHALL result in an `UnknownMessage` error response, not a thrown exception.
|
||||
|
||||
#### Scenario: Request/response round-trip with matching id
|
||||
|
||||
- **WHEN** the client sends a request with `id: 'abc'` and the host replies with `id: 'abc'`
|
||||
- **THEN** the client resolves the pending promise with the response payload
|
||||
|
||||
#### Scenario: Unknown message kind returns error response
|
||||
|
||||
- **WHEN** the host receives a message with `kind: 'unsupported'`
|
||||
- **THEN** the host replies with `kind: 'error'`, error code `NATIVE_MSG_UNKNOWN_KIND`, and does not throw
|
||||
|
||||
#### Scenario: Schema validation rejects malformed payload
|
||||
|
||||
- **WHEN** a message passes transport decryption but its payload fails Zod schema validation
|
||||
- **THEN** the receiver replies with `kind: 'error'`, error code `NATIVE_MSG_SCHEMA_INVALID`
|
||||
|
||||
**Verified by:** `src/client/client.test.ts::"resolves request promise when response id matches"` AND `src/host/host.test.ts::"replies with error for unknown message kind"` AND `src/host/host.test.ts::"replies with schema error for malformed payload"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Key Zeroization on Close
|
||||
|
||||
The package SHALL zeroize all ephemeral key material — handshake private keys, shared secrets, and session keys — when the connection closes or when the client/host is destroyed. Zeroization SHALL use `@suaveplan/crypto-utils` `zeroize()` on every `Uint8Array` holding sensitive material. No copy of the private key SHALL remain in closure captures or event-emitter state after destruction.
|
||||
|
||||
#### Scenario: Destroy zeroizes all key buffers
|
||||
|
||||
- **WHEN** `client.destroy()` is called after a successful handshake
|
||||
- **THEN** every `Uint8Array` that held handshake private key, shared secret, or session keys is filled with zeroes
|
||||
|
||||
#### Scenario: Post-destroy operations throw
|
||||
|
||||
- **WHEN** any method is called on a client or host instance after `destroy()`
|
||||
- **THEN** it throws `NativeMessagingError` with code `NATIVE_MSG_ALREADY_DESTROYED`
|
||||
|
||||
**Verified by:** `src/client/client.test.ts::"zeroizes all key buffers on destroy"` AND `src/host/host.test.ts::"zeroizes all key buffers on destroy"` AND `src/client/client.spec-conformance.test.ts::"throws on post-destroy method calls"`
|
||||
75
openspec/changes/native-messaging/tasks.md
Normal file
75
openspec/changes/native-messaging/tasks.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Tasks — native-messaging
|
||||
|
||||
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify. Retroactive ticking is the failure mode this template guards against.
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [ ] 1.1 Read `MIGRATION.md` Sections 3.5, 4, 8, and 9 for package requirements and sequencing
|
||||
- [ ] 1.2 Read `openspec/AGENTS.md` Sections 13 and 14
|
||||
- [ ] 1.3 Draft `proposal.md` from `openspec/templates/proposal.md`
|
||||
- [ ] 1.4 Draft `specs/native-messaging/spec.md` from `openspec/templates/spec-delta.md`
|
||||
- [ ] 1.5 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
|
||||
|
||||
## 2. Implementation
|
||||
|
||||
- [ ] 2.1 Scaffold package at `packages/plugin/native-messaging` with `package.json`, `tsconfig.json`, `vitest.config.ts`
|
||||
- [ ] 2.2 Implement `Handshake` class in `src/handshake/handshake.ts` using `@suaveplan/crypto-curve` X25519
|
||||
- [ ] 2.3 Implement `FrameEncoder` / `FrameDecoder` in `src/frame/frame.ts` using `@suaveplan/crypto-cipher` ChaCha20-Poly1305
|
||||
- [ ] 2.4 Implement `ChromeTransport`, `FirefoxTransport`, `SafariTransport` in `src/transport/` with common `Transport` interface
|
||||
- [ ] 2.5 Implement `NativeMessagingClient` in `src/client/client.ts` with typed request/response envelope
|
||||
- [ ] 2.6 Implement `NativeMessagingHost` in `src/host/host.ts` with session-per-connection isolation
|
||||
- [ ] 2.7 Implement `ReconnectPolicy` in `src/reconnect/reconnect.ts` with injectable `Clock`
|
||||
- [ ] 2.8 Implement `NativeMessagingError` hierarchy in `src/error/error.ts` with error codes
|
||||
- [ ] 2.9 Add subpath exports in `package.json` for every feature module
|
||||
- [ ] 2.10 Wire Zod schemas at every external boundary (message parse, config validation)
|
||||
- [ ] 2.11 Wire injectable `Rng` through Handshake options and transport config
|
||||
|
||||
## 3. Tests
|
||||
|
||||
- [ ] 3.1 Unit tests at `src/handshake/handshake.test.ts` covering every scenario
|
||||
- [ ] 3.2 Unit tests at `src/frame/frame.test.ts` covering every scenario
|
||||
- [ ] 3.3 Unit tests at `src/transport/transport.test.ts` covering every scenario
|
||||
- [ ] 3.4 Unit tests at `src/client/client.test.ts` covering every scenario
|
||||
- [ ] 3.5 Unit tests at `src/host/host.test.ts` covering every scenario
|
||||
- [ ] 3.6 Unit tests at `src/reconnect/reconnect.test.ts` covering every scenario
|
||||
- [ ] 3.7 Unit tests at `src/error/error.test.ts` covering every scenario
|
||||
- [ ] 3.8 Spec-conformance tests at `src/spec-conformance.test.ts` pinning structural invariants
|
||||
- [ ] 3.9 Integration / contract tests for full client-host encrypted round-trip
|
||||
- [ ] 3.10 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for genuinely async)
|
||||
- [ ] 3.11 Property-based tests for frame encryption round-trip with random payloads
|
||||
|
||||
## 4. Documentation
|
||||
|
||||
- [ ] 4.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference)
|
||||
- [ ] 4.2 README links every co-located `.md` under `## Modules`
|
||||
- [ ] 4.3 Update `CLAUDE.md` package listing
|
||||
|
||||
## 5. Pre-archive verification (the 5-gate triple)
|
||||
|
||||
- [ ] 5.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
|
||||
- [ ] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/native-messaging` — green
|
||||
- [ ] 5.3 `bunx turbo test:coverage --filter=@suaveplan/native-messaging` — 100/100/100/100
|
||||
- [ ] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/native-messaging` — exit 0, no `error` findings
|
||||
- [ ] 5.5 `bunx turbo build --filter=@suaveplan/native-messaging` — green
|
||||
|
||||
## 6. Pre-archive 7-row checklist (AGENTS.md Section 13)
|
||||
|
||||
- [ ] 6.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim
|
||||
- [ ] 6.2 Zod schemas wired at every declared boundary
|
||||
- [ ] 6.3 Every error code in `error-codes.ts` thrown by at least one production path
|
||||
- [ ] 6.4 Strict-mode behaviors tested both on and off
|
||||
- [ ] 6.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider`
|
||||
- [ ] 6.6 Lifecycle dedup tested where claimed (in-flight Promise, idempotent set/load)
|
||||
- [ ] 6.7 No retroactive tick — every box above reflects work done in this change
|
||||
|
||||
## 7. Archive
|
||||
|
||||
- [ ] 7.1 Fold spec delta into `openspec/specs/native-messaging/spec.md`
|
||||
- [ ] 7.2 Move `openspec/changes/native-messaging/` → `openspec/archive/2026-04-29-native-messaging/`
|
||||
- [ ] 7.3 Refresh `MANIFEST.md` if package set or deps changed
|
||||
Reference in New Issue
Block a user