From 2195805dafe3ea6db32f5a9d11a441c4f3b7135e Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Tue, 28 Jul 2026 12:12:29 -0500 Subject: [PATCH 1/3] refactor: complete Tier A+B API surface cleanup Close the remaining high-impact maintenance surface from the staging audit: publish the 1.0 Compatibility Alias kill-list, archive agent run dumps, document NIP-42, deprecate dual Zap/Simple facades toward canonical Public Facades, align NIP-46 validators with canonical key checks, and replace root type star-exports with explicit allowlists. Closes #156 Closes #157 Closes #158 Closes #159 Closes #160 Closes #161 Closes #162 Part of #154 Co-authored-by: Cursor --- CHANGELOG.md | 10 ++ CONTEXT.md | 8 ++ README.md | 2 + docs/adr/0003-plan-public-facade-removals.md | 29 +++++ docs/agents/runs/README.md | 22 ++++ .../coderabbit-local-cleanup-1-8.md | 0 .../runs/{ => archive}/coderabbit-round-1.md | 0 .../runs/{ => archive}/coderabbit-round-2.md | 0 .../runs/{ => archive}/coderabbit-round-3.md | 0 ...erabbit-round-collapse-event-validation.md | 0 .../deep-cleanup-final-coderabbit-local.md | 0 .../final-cleanup-review-session.md | 0 .../integrated-bun-compat-session.md | 0 .../runs/{ => archive}/issue-101-session.md | 0 ...issue-102-loaded-suite-followup-session.md | 0 .../runs/{ => archive}/issue-102-session.md | 0 .../runs/{ => archive}/issue-103-session.md | 0 .../runs/{ => archive}/issue-104-session.md | 0 .../runs/{ => archive}/issue-105-session.md | 0 .../{ => archive}/issue-106-review-packet.md | 0 .../runs/{ => archive}/issue-106-session.md | 0 .../runs/{ => archive}/issue-107-session.md | 0 .../issue-112-coderabbit-local.md | 0 .../{ => archive}/issue-112-review-packet.md | 0 .../runs/{ => archive}/issue-112-session.md | 0 .../issue-113-coderabbit-local.md | 0 .../{ => archive}/issue-113-review-packet.md | 0 .../runs/{ => archive}/issue-113-session.md | 0 .../issue-114-coderabbit-hosted.md | 0 .../issue-114-coderabbit-local.md | 0 .../{ => archive}/issue-114-review-packet.md | 0 .../runs/{ => archive}/issue-114-session.md | 0 .../issue-115-coderabbit-local.md | 0 .../{ => archive}/issue-115-review-packet.md | 0 .../runs/{ => archive}/issue-115-session.md | 0 .../issue-116-coderabbit-local.md | 0 .../{ => archive}/issue-116-review-packet.md | 0 .../runs/{ => archive}/issue-116-session.md | 0 .../issue-117-coderabbit-local.md | 0 .../{ => archive}/issue-117-review-packet.md | 0 .../runs/{ => archive}/issue-117-session.md | 0 .../issue-118-coderabbit-local.md | 0 .../{ => archive}/issue-118-review-packet.md | 0 .../runs/{ => archive}/issue-118-session.md | 0 .../issue-119-coderabbit-hosted.md | 0 .../issue-119-coderabbit-local.md | 0 .../{ => archive}/issue-119-review-packet.md | 0 .../runs/{ => archive}/issue-119-session.md | 0 .../issue-131-coderabbit-local.md | 0 .../{ => archive}/issue-131-review-packet.md | 0 .../runs/{ => archive}/issue-131-session.md | 0 .../issue-132-coderabbit-local.md | 0 .../{ => archive}/issue-132-review-packet.md | 0 .../runs/{ => archive}/issue-132-session.md | 0 .../issue-133-coderabbit-local.md | 0 .../{ => archive}/issue-133-review-packet.md | 0 .../runs/{ => archive}/issue-133-session.md | 0 .../issue-134-coderabbit-local.md | 0 .../{ => archive}/issue-134-review-packet.md | 0 .../runs/{ => archive}/issue-134-session.md | 0 .../issue-135-coderabbit-local.md | 0 .../{ => archive}/issue-135-review-packet.md | 0 .../runs/{ => archive}/issue-135-session.md | 0 .../issue-136-coderabbit-local.md | 0 .../{ => archive}/issue-136-review-packet.md | 0 .../runs/{ => archive}/issue-136-session.md | 0 .../{ => archive}/issue-137-review-packet.md | 0 .../runs/{ => archive}/issue-137-session.md | 0 .../runs/{ => archive}/issue-138-session.md | 0 .../issue-139-coderabbit-local.md | 0 .../{ => archive}/issue-139-review-packet.md | 0 .../runs/{ => archive}/issue-139-session.md | 0 .../runs/{ => archive}/issue-83-session.md | 0 .../runs/{ => archive}/issue-84-session.md | 0 .../runs/{ => archive}/issue-85-session.md | 0 .../{ => archive}/issue-89-review-packet.md | 0 .../runs/{ => archive}/issue-89-session.md | 0 .../{ => archive}/issue-90-review-packet.md | 0 .../runs/{ => archive}/issue-90-session.md | 0 .../{ => archive}/issue-91-review-packet.md | 0 .../runs/{ => archive}/issue-91-session.md | 0 .../{ => archive}/issue-94-review-packet.md | 0 .../runs/{ => archive}/issue-94-session.md | 0 .../{ => archive}/issue-95-review-packet.md | 0 .../runs/{ => archive}/issue-95-session.md | 0 .../{ => archive}/issue-96-review-packet.md | 0 .../runs/{ => archive}/issue-96-session.md | 0 .../{ => archive}/issue-97-review-packet.md | 0 .../runs/{ => archive}/issue-97-session.md | 0 .../{ => archive}/issue-98-review-packet.md | 0 .../runs/{ => archive}/issue-98-session.md | 0 .../runs/{ => archive}/review-83-packet.md | 0 .../runs/{ => archive}/review-84-packet.md | 0 .../runs/{ => archive}/review-85-packet.md | 0 .../runs/feature-cleanup-tier-ab-ledger.md | 83 +++++++++++++ examples/nip46/README.md | 4 + src/entries/index.web.ts | 79 ++++++++---- src/index.ts | 79 ++++++++---- src/nip42/README.md | 66 +++++++++++ src/nip46/README.md | 49 ++++---- src/nip46/index.ts | 30 +++-- src/nip46/internal/wire.ts | 4 +- src/nip46/simple-bunker.ts | 3 + src/nip46/simple-client.ts | 7 +- src/nip46/utils/connection.ts | 8 +- src/nip46/utils/validator.ts | 47 ++++++-- src/nip57/README.md | 2 + src/nip57/client.ts | 9 +- src/nip57/index.ts | 2 +- tests/nip46/validator-unit.test.ts | 112 ++++++++++++------ 110 files changed, 520 insertions(+), 135 deletions(-) create mode 100644 docs/adr/0003-plan-public-facade-removals.md create mode 100644 docs/agents/runs/README.md rename docs/agents/runs/{ => archive}/coderabbit-local-cleanup-1-8.md (100%) rename docs/agents/runs/{ => archive}/coderabbit-round-1.md (100%) rename docs/agents/runs/{ => archive}/coderabbit-round-2.md (100%) rename docs/agents/runs/{ => archive}/coderabbit-round-3.md (100%) rename docs/agents/runs/{ => archive}/coderabbit-round-collapse-event-validation.md (100%) rename docs/agents/runs/{ => archive}/deep-cleanup-final-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/final-cleanup-review-session.md (100%) rename docs/agents/runs/{ => archive}/integrated-bun-compat-session.md (100%) rename docs/agents/runs/{ => archive}/issue-101-session.md (100%) rename docs/agents/runs/{ => archive}/issue-102-loaded-suite-followup-session.md (100%) rename docs/agents/runs/{ => archive}/issue-102-session.md (100%) rename docs/agents/runs/{ => archive}/issue-103-session.md (100%) rename docs/agents/runs/{ => archive}/issue-104-session.md (100%) rename docs/agents/runs/{ => archive}/issue-105-session.md (100%) rename docs/agents/runs/{ => archive}/issue-106-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-106-session.md (100%) rename docs/agents/runs/{ => archive}/issue-107-session.md (100%) rename docs/agents/runs/{ => archive}/issue-112-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-112-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-112-session.md (100%) rename docs/agents/runs/{ => archive}/issue-113-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-113-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-113-session.md (100%) rename docs/agents/runs/{ => archive}/issue-114-coderabbit-hosted.md (100%) rename docs/agents/runs/{ => archive}/issue-114-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-114-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-114-session.md (100%) rename docs/agents/runs/{ => archive}/issue-115-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-115-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-115-session.md (100%) rename docs/agents/runs/{ => archive}/issue-116-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-116-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-116-session.md (100%) rename docs/agents/runs/{ => archive}/issue-117-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-117-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-117-session.md (100%) rename docs/agents/runs/{ => archive}/issue-118-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-118-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-118-session.md (100%) rename docs/agents/runs/{ => archive}/issue-119-coderabbit-hosted.md (100%) rename docs/agents/runs/{ => archive}/issue-119-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-119-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-119-session.md (100%) rename docs/agents/runs/{ => archive}/issue-131-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-131-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-131-session.md (100%) rename docs/agents/runs/{ => archive}/issue-132-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-132-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-132-session.md (100%) rename docs/agents/runs/{ => archive}/issue-133-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-133-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-133-session.md (100%) rename docs/agents/runs/{ => archive}/issue-134-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-134-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-134-session.md (100%) rename docs/agents/runs/{ => archive}/issue-135-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-135-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-135-session.md (100%) rename docs/agents/runs/{ => archive}/issue-136-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-136-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-136-session.md (100%) rename docs/agents/runs/{ => archive}/issue-137-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-137-session.md (100%) rename docs/agents/runs/{ => archive}/issue-138-session.md (100%) rename docs/agents/runs/{ => archive}/issue-139-coderabbit-local.md (100%) rename docs/agents/runs/{ => archive}/issue-139-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-139-session.md (100%) rename docs/agents/runs/{ => archive}/issue-83-session.md (100%) rename docs/agents/runs/{ => archive}/issue-84-session.md (100%) rename docs/agents/runs/{ => archive}/issue-85-session.md (100%) rename docs/agents/runs/{ => archive}/issue-89-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-89-session.md (100%) rename docs/agents/runs/{ => archive}/issue-90-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-90-session.md (100%) rename docs/agents/runs/{ => archive}/issue-91-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-91-session.md (100%) rename docs/agents/runs/{ => archive}/issue-94-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-94-session.md (100%) rename docs/agents/runs/{ => archive}/issue-95-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-95-session.md (100%) rename docs/agents/runs/{ => archive}/issue-96-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-96-session.md (100%) rename docs/agents/runs/{ => archive}/issue-97-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-97-session.md (100%) rename docs/agents/runs/{ => archive}/issue-98-review-packet.md (100%) rename docs/agents/runs/{ => archive}/issue-98-session.md (100%) rename docs/agents/runs/{ => archive}/review-83-packet.md (100%) rename docs/agents/runs/{ => archive}/review-84-packet.md (100%) rename docs/agents/runs/{ => archive}/review-85-packet.md (100%) create mode 100644 docs/agents/runs/feature-cleanup-tier-ab-ledger.md create mode 100644 src/nip42/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e1b0e8..b02caabe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A supported `snstr/testing` subpath now owns the Node-only `NostrRelay` test Relay and framework-neutral Relay test-double types without leaking Jest into published application declarations. - Canonical NIP-01 client and Relay wire-message tuple types now provide one authoritative protocol definition. - Repository tooling now enforces npm 9.8.1 as the release package manager, Bun as a pinned compatibility runner, and explicit routine, slow, and complete test lanes. +- ADR 0003 records the planned next-major Public Facade consolidations and Compatibility Alias kill-list without removing 0.x aliases yet. +- NIP-42 module README documents the public Relay auth helpers. ### Changed - Relay event storage, Nostr Relay registry management, NIP-47 protocol codecs and dispatch, NIP-46 request machinery, NIP-57 client behavior, and ephemeral Relay transport/session/filter responsibilities now live behind smaller internal modules while preserving their public 0.x facades. - Production diagnostics now use one compatible logger policy across Nostr, Relay, RelayPool, NIP-46, NIP-47, NIP-57, and stateless protocol helpers. - Structural regression tests now prefer public behavior and owned testing seams over private implementation shapes. +- Agent run session dumps are archived under `docs/agents/runs/archive/` with durable ledgers kept in the runs index. +- Root and browser entry points now export the public NIP-01 type surface through explicit allowlists instead of star re-exports. + +### Deprecated +- NIP-57 `ZapClient` is deprecated in favor of the canonical `NostrZapClient` facade. +- NIP-46 `SimpleNIP46Client` and `SimpleNIP46Bunker` are deprecated in favor of the full `NostrRemoteSignerClient` and `NostrRemoteSignerBunker` facades. +- NIP-46 boolean validators are renamed to `isValidNip46*`, with the previous `validate*` names retained as Compatibility Aliases. ### Fixed - NIP-44 decryption now rejects unsupported legacy payload versions and malformed v2 nonce sizes through the public decrypt path. @@ -26,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security - NIP-46 connection secrets, private keys, decrypted payloads, and untrusted error text are excluded from diagnostic output. - Generic key, wire-format, and resource-limit validation now has canonical ownership so NIP-specific policies cannot silently drift. +- NIP-46 `isValidNip46PrivateKey` (and its deprecated `validatePrivateKey` alias) now rejects keys outside the secp256k1 curve order, matching canonical key-validation. ## [0.5.0] - 2026-07-18 diff --git a/CONTEXT.md b/CONTEXT.md index e273ab24..562d4fcd 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -23,3 +23,11 @@ _Avoid_: Query, search parameters **NIP**: A Nostr Implementation Possibility that defines a protocol behavior or interoperable extension. _Avoid_: Plugin, feature spec + +**Public Facade**: +The supported public class or function that callers should use for a capability when multiple wrappers exist. +_Avoid_: Primary API, recommended entrypoint (use this term instead) + +**Compatibility Alias**: +A deprecated public export retained for 0.x consumers until the next major release removes it. +_Avoid_: Legacy shim, soft export diff --git a/README.md b/README.md index ad1a1084..725c18ac 100644 --- a/README.md +++ b/README.md @@ -422,6 +422,8 @@ The project is organized with detailed documentation for different components: - **[NIP-17](src/nip17/README.md)**: Gift wrapped direct messages - **[NIP-19](src/nip19/README.md)**: Bech32-encoded entities - **[NIP-21](src/nip21/README.md)**: URI scheme for nostr links +- **[NIP-29](src/nip29/README.md)**: Relay-based groups +- **[NIP-42](src/nip42/README.md)**: Client authentication to relays - **[NIP-44](src/nip44/README.md)**: Versioned encryption - **[NIP-46](src/nip46/README.md)**: Remote signing protocol - **[NIP-47](src/nip47/README.md)**: Nostr Wallet Connect diff --git a/docs/adr/0003-plan-public-facade-removals.md b/docs/adr/0003-plan-public-facade-removals.md new file mode 100644 index 00000000..0f40cca6 --- /dev/null +++ b/docs/adr/0003-plan-public-facade-removals.md @@ -0,0 +1,29 @@ +# Plan Public Facade consolidations and Compatibility Alias removals + +SNSTR will keep Compatibility Aliases through the remaining 0.x line and remove them deliberately in the next major release. Each dual Public Facade gains one canonical replacement now; deprecated aliases stay exported and documented until that major bump. This preserves downstream imports while making the supported surface intentional for agents and humans. + +## Kill list (planned next major) + +| Compatibility Alias / dual | Canonical replacement | Notes | +| --- | --- | --- | +| `ZapClient` | `NostrZapClient` | NIP-57 Public Facade | +| `SimpleNIP46Client` / `SimpleNIP46Bunker` | `NostrRemoteSignerClient` / `NostrRemoteSignerBunker` | Simple is demo/compat only | +| `initializeCrypto` (root/web NIP-17 alias) | `initializeNIP17Crypto` | Already `@deprecated` | +| `nip19.validateRelayUrl` | `isValidRelayUrl` from NIP-19 secure helpers | Already `@deprecated` | +| `RelayInformation` | `RelayInfo` | Already `@deprecated` | +| NIP-47 `LogArgument` / logger type aliases | Root `DiagnosticLogArgument` / `DiagnosticLogger` | ADR 0002 | +| NIP-02 `Logger` warn-only alias | `WarningLogger` / root diagnostics | ADR 0002 | +| NIP-46 `getPublicKey` | `getUserPublicKey` | Already `@deprecated` | +| NIP-46 boolean validators named like `validateEventContent` / `validatePrivateKey` / overlapping peers | NIP-specific `isValidNip46*` names | Keep old names as aliases until major | + +## Considered Options + +- Remove Compatibility Aliases immediately in 0.x. Rejected because it breaks consumers without a major version signal. +- Keep dual Public Facades indefinitely without deprecation. Rejected because agents and docs treat both as first-class and the surfaces drift. + +## Consequences + +- 0.x work may deprecate and document, but must not delete listed aliases. +- New code and examples should prefer the canonical Public Facade. +- Simple NIP-46 must not gain new capabilities; Full is the production path. +- Root/web type exports should be explicit allowlists that still include listed aliases until the major removal. diff --git a/docs/agents/runs/README.md b/docs/agents/runs/README.md new file mode 100644 index 00000000..1693ef4c --- /dev/null +++ b/docs/agents/runs/README.md @@ -0,0 +1,22 @@ +# Agent run artifacts + +Durable Feature Dev / cleanup run ledgers live in this directory. Verbose per-issue session dumps, review packets, and CodeRabbit round logs are archived under [`archive/`](./archive/). + +## Current ledgers + +| File | Run | +| --- | --- | +| [`feature-cleanup-tier-ab-ledger.md`](./feature-cleanup-tier-ab-ledger.md) | Tier A+B API surface cleanup (#154) | +| [`cleanup-1-9-ledger.md`](./cleanup-1-9-ledger.md) | High-impact cleanup 1–9 (#130) | +| [`deep-cleanup-1-8-ledger.md`](./deep-cleanup-1-8-ledger.md) | Deep cleanup 1–8 (#111) | +| [`deep-cleanup-final-audit.md`](./deep-cleanup-final-audit.md) | Final staging audit after deep cleanup | +| [`feature-cleanup-runtime-debt-ledger.md`](./feature-cleanup-runtime-debt-ledger.md) | Runtime debt cleanup (#100) | +| [`feature-cleanup-1-8-ledger.md`](./feature-cleanup-1-8-ledger.md) | Earlier cleanup 1–8 feature ledger | +| [`feature-cleanup-1-3-ledger.md`](./feature-cleanup-1-3-ledger.md) | Earlier cleanup 1–3 feature ledger | +| [`feature-collapse-event-validation-ledger.md`](./feature-collapse-event-validation-ledger.md) | Event validation collapse | + +## Policy + +- Prefer updating the active run ledger over creating new session dumps at the top level. +- If a full issue session record is required by the Feature Dev loop, store it under `archive/` once the issue is closed, or keep only the ledger row with the commit SHA. +- Do not delete archived evidence; move it. diff --git a/docs/agents/runs/coderabbit-local-cleanup-1-8.md b/docs/agents/runs/archive/coderabbit-local-cleanup-1-8.md similarity index 100% rename from docs/agents/runs/coderabbit-local-cleanup-1-8.md rename to docs/agents/runs/archive/coderabbit-local-cleanup-1-8.md diff --git a/docs/agents/runs/coderabbit-round-1.md b/docs/agents/runs/archive/coderabbit-round-1.md similarity index 100% rename from docs/agents/runs/coderabbit-round-1.md rename to docs/agents/runs/archive/coderabbit-round-1.md diff --git a/docs/agents/runs/coderabbit-round-2.md b/docs/agents/runs/archive/coderabbit-round-2.md similarity index 100% rename from docs/agents/runs/coderabbit-round-2.md rename to docs/agents/runs/archive/coderabbit-round-2.md diff --git a/docs/agents/runs/coderabbit-round-3.md b/docs/agents/runs/archive/coderabbit-round-3.md similarity index 100% rename from docs/agents/runs/coderabbit-round-3.md rename to docs/agents/runs/archive/coderabbit-round-3.md diff --git a/docs/agents/runs/coderabbit-round-collapse-event-validation.md b/docs/agents/runs/archive/coderabbit-round-collapse-event-validation.md similarity index 100% rename from docs/agents/runs/coderabbit-round-collapse-event-validation.md rename to docs/agents/runs/archive/coderabbit-round-collapse-event-validation.md diff --git a/docs/agents/runs/deep-cleanup-final-coderabbit-local.md b/docs/agents/runs/archive/deep-cleanup-final-coderabbit-local.md similarity index 100% rename from docs/agents/runs/deep-cleanup-final-coderabbit-local.md rename to docs/agents/runs/archive/deep-cleanup-final-coderabbit-local.md diff --git a/docs/agents/runs/final-cleanup-review-session.md b/docs/agents/runs/archive/final-cleanup-review-session.md similarity index 100% rename from docs/agents/runs/final-cleanup-review-session.md rename to docs/agents/runs/archive/final-cleanup-review-session.md diff --git a/docs/agents/runs/integrated-bun-compat-session.md b/docs/agents/runs/archive/integrated-bun-compat-session.md similarity index 100% rename from docs/agents/runs/integrated-bun-compat-session.md rename to docs/agents/runs/archive/integrated-bun-compat-session.md diff --git a/docs/agents/runs/issue-101-session.md b/docs/agents/runs/archive/issue-101-session.md similarity index 100% rename from docs/agents/runs/issue-101-session.md rename to docs/agents/runs/archive/issue-101-session.md diff --git a/docs/agents/runs/issue-102-loaded-suite-followup-session.md b/docs/agents/runs/archive/issue-102-loaded-suite-followup-session.md similarity index 100% rename from docs/agents/runs/issue-102-loaded-suite-followup-session.md rename to docs/agents/runs/archive/issue-102-loaded-suite-followup-session.md diff --git a/docs/agents/runs/issue-102-session.md b/docs/agents/runs/archive/issue-102-session.md similarity index 100% rename from docs/agents/runs/issue-102-session.md rename to docs/agents/runs/archive/issue-102-session.md diff --git a/docs/agents/runs/issue-103-session.md b/docs/agents/runs/archive/issue-103-session.md similarity index 100% rename from docs/agents/runs/issue-103-session.md rename to docs/agents/runs/archive/issue-103-session.md diff --git a/docs/agents/runs/issue-104-session.md b/docs/agents/runs/archive/issue-104-session.md similarity index 100% rename from docs/agents/runs/issue-104-session.md rename to docs/agents/runs/archive/issue-104-session.md diff --git a/docs/agents/runs/issue-105-session.md b/docs/agents/runs/archive/issue-105-session.md similarity index 100% rename from docs/agents/runs/issue-105-session.md rename to docs/agents/runs/archive/issue-105-session.md diff --git a/docs/agents/runs/issue-106-review-packet.md b/docs/agents/runs/archive/issue-106-review-packet.md similarity index 100% rename from docs/agents/runs/issue-106-review-packet.md rename to docs/agents/runs/archive/issue-106-review-packet.md diff --git a/docs/agents/runs/issue-106-session.md b/docs/agents/runs/archive/issue-106-session.md similarity index 100% rename from docs/agents/runs/issue-106-session.md rename to docs/agents/runs/archive/issue-106-session.md diff --git a/docs/agents/runs/issue-107-session.md b/docs/agents/runs/archive/issue-107-session.md similarity index 100% rename from docs/agents/runs/issue-107-session.md rename to docs/agents/runs/archive/issue-107-session.md diff --git a/docs/agents/runs/issue-112-coderabbit-local.md b/docs/agents/runs/archive/issue-112-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-112-coderabbit-local.md rename to docs/agents/runs/archive/issue-112-coderabbit-local.md diff --git a/docs/agents/runs/issue-112-review-packet.md b/docs/agents/runs/archive/issue-112-review-packet.md similarity index 100% rename from docs/agents/runs/issue-112-review-packet.md rename to docs/agents/runs/archive/issue-112-review-packet.md diff --git a/docs/agents/runs/issue-112-session.md b/docs/agents/runs/archive/issue-112-session.md similarity index 100% rename from docs/agents/runs/issue-112-session.md rename to docs/agents/runs/archive/issue-112-session.md diff --git a/docs/agents/runs/issue-113-coderabbit-local.md b/docs/agents/runs/archive/issue-113-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-113-coderabbit-local.md rename to docs/agents/runs/archive/issue-113-coderabbit-local.md diff --git a/docs/agents/runs/issue-113-review-packet.md b/docs/agents/runs/archive/issue-113-review-packet.md similarity index 100% rename from docs/agents/runs/issue-113-review-packet.md rename to docs/agents/runs/archive/issue-113-review-packet.md diff --git a/docs/agents/runs/issue-113-session.md b/docs/agents/runs/archive/issue-113-session.md similarity index 100% rename from docs/agents/runs/issue-113-session.md rename to docs/agents/runs/archive/issue-113-session.md diff --git a/docs/agents/runs/issue-114-coderabbit-hosted.md b/docs/agents/runs/archive/issue-114-coderabbit-hosted.md similarity index 100% rename from docs/agents/runs/issue-114-coderabbit-hosted.md rename to docs/agents/runs/archive/issue-114-coderabbit-hosted.md diff --git a/docs/agents/runs/issue-114-coderabbit-local.md b/docs/agents/runs/archive/issue-114-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-114-coderabbit-local.md rename to docs/agents/runs/archive/issue-114-coderabbit-local.md diff --git a/docs/agents/runs/issue-114-review-packet.md b/docs/agents/runs/archive/issue-114-review-packet.md similarity index 100% rename from docs/agents/runs/issue-114-review-packet.md rename to docs/agents/runs/archive/issue-114-review-packet.md diff --git a/docs/agents/runs/issue-114-session.md b/docs/agents/runs/archive/issue-114-session.md similarity index 100% rename from docs/agents/runs/issue-114-session.md rename to docs/agents/runs/archive/issue-114-session.md diff --git a/docs/agents/runs/issue-115-coderabbit-local.md b/docs/agents/runs/archive/issue-115-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-115-coderabbit-local.md rename to docs/agents/runs/archive/issue-115-coderabbit-local.md diff --git a/docs/agents/runs/issue-115-review-packet.md b/docs/agents/runs/archive/issue-115-review-packet.md similarity index 100% rename from docs/agents/runs/issue-115-review-packet.md rename to docs/agents/runs/archive/issue-115-review-packet.md diff --git a/docs/agents/runs/issue-115-session.md b/docs/agents/runs/archive/issue-115-session.md similarity index 100% rename from docs/agents/runs/issue-115-session.md rename to docs/agents/runs/archive/issue-115-session.md diff --git a/docs/agents/runs/issue-116-coderabbit-local.md b/docs/agents/runs/archive/issue-116-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-116-coderabbit-local.md rename to docs/agents/runs/archive/issue-116-coderabbit-local.md diff --git a/docs/agents/runs/issue-116-review-packet.md b/docs/agents/runs/archive/issue-116-review-packet.md similarity index 100% rename from docs/agents/runs/issue-116-review-packet.md rename to docs/agents/runs/archive/issue-116-review-packet.md diff --git a/docs/agents/runs/issue-116-session.md b/docs/agents/runs/archive/issue-116-session.md similarity index 100% rename from docs/agents/runs/issue-116-session.md rename to docs/agents/runs/archive/issue-116-session.md diff --git a/docs/agents/runs/issue-117-coderabbit-local.md b/docs/agents/runs/archive/issue-117-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-117-coderabbit-local.md rename to docs/agents/runs/archive/issue-117-coderabbit-local.md diff --git a/docs/agents/runs/issue-117-review-packet.md b/docs/agents/runs/archive/issue-117-review-packet.md similarity index 100% rename from docs/agents/runs/issue-117-review-packet.md rename to docs/agents/runs/archive/issue-117-review-packet.md diff --git a/docs/agents/runs/issue-117-session.md b/docs/agents/runs/archive/issue-117-session.md similarity index 100% rename from docs/agents/runs/issue-117-session.md rename to docs/agents/runs/archive/issue-117-session.md diff --git a/docs/agents/runs/issue-118-coderabbit-local.md b/docs/agents/runs/archive/issue-118-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-118-coderabbit-local.md rename to docs/agents/runs/archive/issue-118-coderabbit-local.md diff --git a/docs/agents/runs/issue-118-review-packet.md b/docs/agents/runs/archive/issue-118-review-packet.md similarity index 100% rename from docs/agents/runs/issue-118-review-packet.md rename to docs/agents/runs/archive/issue-118-review-packet.md diff --git a/docs/agents/runs/issue-118-session.md b/docs/agents/runs/archive/issue-118-session.md similarity index 100% rename from docs/agents/runs/issue-118-session.md rename to docs/agents/runs/archive/issue-118-session.md diff --git a/docs/agents/runs/issue-119-coderabbit-hosted.md b/docs/agents/runs/archive/issue-119-coderabbit-hosted.md similarity index 100% rename from docs/agents/runs/issue-119-coderabbit-hosted.md rename to docs/agents/runs/archive/issue-119-coderabbit-hosted.md diff --git a/docs/agents/runs/issue-119-coderabbit-local.md b/docs/agents/runs/archive/issue-119-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-119-coderabbit-local.md rename to docs/agents/runs/archive/issue-119-coderabbit-local.md diff --git a/docs/agents/runs/issue-119-review-packet.md b/docs/agents/runs/archive/issue-119-review-packet.md similarity index 100% rename from docs/agents/runs/issue-119-review-packet.md rename to docs/agents/runs/archive/issue-119-review-packet.md diff --git a/docs/agents/runs/issue-119-session.md b/docs/agents/runs/archive/issue-119-session.md similarity index 100% rename from docs/agents/runs/issue-119-session.md rename to docs/agents/runs/archive/issue-119-session.md diff --git a/docs/agents/runs/issue-131-coderabbit-local.md b/docs/agents/runs/archive/issue-131-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-131-coderabbit-local.md rename to docs/agents/runs/archive/issue-131-coderabbit-local.md diff --git a/docs/agents/runs/issue-131-review-packet.md b/docs/agents/runs/archive/issue-131-review-packet.md similarity index 100% rename from docs/agents/runs/issue-131-review-packet.md rename to docs/agents/runs/archive/issue-131-review-packet.md diff --git a/docs/agents/runs/issue-131-session.md b/docs/agents/runs/archive/issue-131-session.md similarity index 100% rename from docs/agents/runs/issue-131-session.md rename to docs/agents/runs/archive/issue-131-session.md diff --git a/docs/agents/runs/issue-132-coderabbit-local.md b/docs/agents/runs/archive/issue-132-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-132-coderabbit-local.md rename to docs/agents/runs/archive/issue-132-coderabbit-local.md diff --git a/docs/agents/runs/issue-132-review-packet.md b/docs/agents/runs/archive/issue-132-review-packet.md similarity index 100% rename from docs/agents/runs/issue-132-review-packet.md rename to docs/agents/runs/archive/issue-132-review-packet.md diff --git a/docs/agents/runs/issue-132-session.md b/docs/agents/runs/archive/issue-132-session.md similarity index 100% rename from docs/agents/runs/issue-132-session.md rename to docs/agents/runs/archive/issue-132-session.md diff --git a/docs/agents/runs/issue-133-coderabbit-local.md b/docs/agents/runs/archive/issue-133-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-133-coderabbit-local.md rename to docs/agents/runs/archive/issue-133-coderabbit-local.md diff --git a/docs/agents/runs/issue-133-review-packet.md b/docs/agents/runs/archive/issue-133-review-packet.md similarity index 100% rename from docs/agents/runs/issue-133-review-packet.md rename to docs/agents/runs/archive/issue-133-review-packet.md diff --git a/docs/agents/runs/issue-133-session.md b/docs/agents/runs/archive/issue-133-session.md similarity index 100% rename from docs/agents/runs/issue-133-session.md rename to docs/agents/runs/archive/issue-133-session.md diff --git a/docs/agents/runs/issue-134-coderabbit-local.md b/docs/agents/runs/archive/issue-134-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-134-coderabbit-local.md rename to docs/agents/runs/archive/issue-134-coderabbit-local.md diff --git a/docs/agents/runs/issue-134-review-packet.md b/docs/agents/runs/archive/issue-134-review-packet.md similarity index 100% rename from docs/agents/runs/issue-134-review-packet.md rename to docs/agents/runs/archive/issue-134-review-packet.md diff --git a/docs/agents/runs/issue-134-session.md b/docs/agents/runs/archive/issue-134-session.md similarity index 100% rename from docs/agents/runs/issue-134-session.md rename to docs/agents/runs/archive/issue-134-session.md diff --git a/docs/agents/runs/issue-135-coderabbit-local.md b/docs/agents/runs/archive/issue-135-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-135-coderabbit-local.md rename to docs/agents/runs/archive/issue-135-coderabbit-local.md diff --git a/docs/agents/runs/issue-135-review-packet.md b/docs/agents/runs/archive/issue-135-review-packet.md similarity index 100% rename from docs/agents/runs/issue-135-review-packet.md rename to docs/agents/runs/archive/issue-135-review-packet.md diff --git a/docs/agents/runs/issue-135-session.md b/docs/agents/runs/archive/issue-135-session.md similarity index 100% rename from docs/agents/runs/issue-135-session.md rename to docs/agents/runs/archive/issue-135-session.md diff --git a/docs/agents/runs/issue-136-coderabbit-local.md b/docs/agents/runs/archive/issue-136-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-136-coderabbit-local.md rename to docs/agents/runs/archive/issue-136-coderabbit-local.md diff --git a/docs/agents/runs/issue-136-review-packet.md b/docs/agents/runs/archive/issue-136-review-packet.md similarity index 100% rename from docs/agents/runs/issue-136-review-packet.md rename to docs/agents/runs/archive/issue-136-review-packet.md diff --git a/docs/agents/runs/issue-136-session.md b/docs/agents/runs/archive/issue-136-session.md similarity index 100% rename from docs/agents/runs/issue-136-session.md rename to docs/agents/runs/archive/issue-136-session.md diff --git a/docs/agents/runs/issue-137-review-packet.md b/docs/agents/runs/archive/issue-137-review-packet.md similarity index 100% rename from docs/agents/runs/issue-137-review-packet.md rename to docs/agents/runs/archive/issue-137-review-packet.md diff --git a/docs/agents/runs/issue-137-session.md b/docs/agents/runs/archive/issue-137-session.md similarity index 100% rename from docs/agents/runs/issue-137-session.md rename to docs/agents/runs/archive/issue-137-session.md diff --git a/docs/agents/runs/issue-138-session.md b/docs/agents/runs/archive/issue-138-session.md similarity index 100% rename from docs/agents/runs/issue-138-session.md rename to docs/agents/runs/archive/issue-138-session.md diff --git a/docs/agents/runs/issue-139-coderabbit-local.md b/docs/agents/runs/archive/issue-139-coderabbit-local.md similarity index 100% rename from docs/agents/runs/issue-139-coderabbit-local.md rename to docs/agents/runs/archive/issue-139-coderabbit-local.md diff --git a/docs/agents/runs/issue-139-review-packet.md b/docs/agents/runs/archive/issue-139-review-packet.md similarity index 100% rename from docs/agents/runs/issue-139-review-packet.md rename to docs/agents/runs/archive/issue-139-review-packet.md diff --git a/docs/agents/runs/issue-139-session.md b/docs/agents/runs/archive/issue-139-session.md similarity index 100% rename from docs/agents/runs/issue-139-session.md rename to docs/agents/runs/archive/issue-139-session.md diff --git a/docs/agents/runs/issue-83-session.md b/docs/agents/runs/archive/issue-83-session.md similarity index 100% rename from docs/agents/runs/issue-83-session.md rename to docs/agents/runs/archive/issue-83-session.md diff --git a/docs/agents/runs/issue-84-session.md b/docs/agents/runs/archive/issue-84-session.md similarity index 100% rename from docs/agents/runs/issue-84-session.md rename to docs/agents/runs/archive/issue-84-session.md diff --git a/docs/agents/runs/issue-85-session.md b/docs/agents/runs/archive/issue-85-session.md similarity index 100% rename from docs/agents/runs/issue-85-session.md rename to docs/agents/runs/archive/issue-85-session.md diff --git a/docs/agents/runs/issue-89-review-packet.md b/docs/agents/runs/archive/issue-89-review-packet.md similarity index 100% rename from docs/agents/runs/issue-89-review-packet.md rename to docs/agents/runs/archive/issue-89-review-packet.md diff --git a/docs/agents/runs/issue-89-session.md b/docs/agents/runs/archive/issue-89-session.md similarity index 100% rename from docs/agents/runs/issue-89-session.md rename to docs/agents/runs/archive/issue-89-session.md diff --git a/docs/agents/runs/issue-90-review-packet.md b/docs/agents/runs/archive/issue-90-review-packet.md similarity index 100% rename from docs/agents/runs/issue-90-review-packet.md rename to docs/agents/runs/archive/issue-90-review-packet.md diff --git a/docs/agents/runs/issue-90-session.md b/docs/agents/runs/archive/issue-90-session.md similarity index 100% rename from docs/agents/runs/issue-90-session.md rename to docs/agents/runs/archive/issue-90-session.md diff --git a/docs/agents/runs/issue-91-review-packet.md b/docs/agents/runs/archive/issue-91-review-packet.md similarity index 100% rename from docs/agents/runs/issue-91-review-packet.md rename to docs/agents/runs/archive/issue-91-review-packet.md diff --git a/docs/agents/runs/issue-91-session.md b/docs/agents/runs/archive/issue-91-session.md similarity index 100% rename from docs/agents/runs/issue-91-session.md rename to docs/agents/runs/archive/issue-91-session.md diff --git a/docs/agents/runs/issue-94-review-packet.md b/docs/agents/runs/archive/issue-94-review-packet.md similarity index 100% rename from docs/agents/runs/issue-94-review-packet.md rename to docs/agents/runs/archive/issue-94-review-packet.md diff --git a/docs/agents/runs/issue-94-session.md b/docs/agents/runs/archive/issue-94-session.md similarity index 100% rename from docs/agents/runs/issue-94-session.md rename to docs/agents/runs/archive/issue-94-session.md diff --git a/docs/agents/runs/issue-95-review-packet.md b/docs/agents/runs/archive/issue-95-review-packet.md similarity index 100% rename from docs/agents/runs/issue-95-review-packet.md rename to docs/agents/runs/archive/issue-95-review-packet.md diff --git a/docs/agents/runs/issue-95-session.md b/docs/agents/runs/archive/issue-95-session.md similarity index 100% rename from docs/agents/runs/issue-95-session.md rename to docs/agents/runs/archive/issue-95-session.md diff --git a/docs/agents/runs/issue-96-review-packet.md b/docs/agents/runs/archive/issue-96-review-packet.md similarity index 100% rename from docs/agents/runs/issue-96-review-packet.md rename to docs/agents/runs/archive/issue-96-review-packet.md diff --git a/docs/agents/runs/issue-96-session.md b/docs/agents/runs/archive/issue-96-session.md similarity index 100% rename from docs/agents/runs/issue-96-session.md rename to docs/agents/runs/archive/issue-96-session.md diff --git a/docs/agents/runs/issue-97-review-packet.md b/docs/agents/runs/archive/issue-97-review-packet.md similarity index 100% rename from docs/agents/runs/issue-97-review-packet.md rename to docs/agents/runs/archive/issue-97-review-packet.md diff --git a/docs/agents/runs/issue-97-session.md b/docs/agents/runs/archive/issue-97-session.md similarity index 100% rename from docs/agents/runs/issue-97-session.md rename to docs/agents/runs/archive/issue-97-session.md diff --git a/docs/agents/runs/issue-98-review-packet.md b/docs/agents/runs/archive/issue-98-review-packet.md similarity index 100% rename from docs/agents/runs/issue-98-review-packet.md rename to docs/agents/runs/archive/issue-98-review-packet.md diff --git a/docs/agents/runs/issue-98-session.md b/docs/agents/runs/archive/issue-98-session.md similarity index 100% rename from docs/agents/runs/issue-98-session.md rename to docs/agents/runs/archive/issue-98-session.md diff --git a/docs/agents/runs/review-83-packet.md b/docs/agents/runs/archive/review-83-packet.md similarity index 100% rename from docs/agents/runs/review-83-packet.md rename to docs/agents/runs/archive/review-83-packet.md diff --git a/docs/agents/runs/review-84-packet.md b/docs/agents/runs/archive/review-84-packet.md similarity index 100% rename from docs/agents/runs/review-84-packet.md rename to docs/agents/runs/archive/review-84-packet.md diff --git a/docs/agents/runs/review-85-packet.md b/docs/agents/runs/archive/review-85-packet.md similarity index 100% rename from docs/agents/runs/review-85-packet.md rename to docs/agents/runs/archive/review-85-packet.md diff --git a/docs/agents/runs/feature-cleanup-tier-ab-ledger.md b/docs/agents/runs/feature-cleanup-tier-ab-ledger.md new file mode 100644 index 00000000..60d5152b --- /dev/null +++ b/docs/agents/runs/feature-cleanup-tier-ab-ledger.md @@ -0,0 +1,83 @@ +# Goal Ledger: Tier A+B API Surface Cleanup + +## Run + +- Run ID: `snstr-cleanup-tier-ab-20260728` +- Loop: feature-dev +- Target repo: `snstr` +- Base branch: `staging` +- Feature branch: `feature/cleanup-tier-ab-api-surface` +- Human owner: plebdev +- Started: 2026-07-28 +- Current status: implementation + review fixes complete; preparing commits, CodeRabbit, staging PR +- Skill setup status: present and verified + +## Goal + +Complete Tier A and Tier B cleanup end to end: close stale shipped issues, publish a 1.0 deprecation kill-list, archive agent run dumps, add NIP-42 docs, collapse/deprecate dual public facades (NIP-57 Zap clients, NIP-46 Simple vs Full), align overlapping NIP-46 validators with canonical security paths, and replace root `export *` type dumps with an explicit allowlist — then open a non-draft PR into `staging`. + +## Durable Artifacts + +- CONTEXT updates: Public Facade, Compatibility Alias +- ADRs: `docs/adr/0003-plan-public-facade-removals.md` +- Prototype source branch, if any: none +- Spec issue: [#154](https://github.com/AustinKelsay/snstr/issues/154) +- Tickets: #155–#162 +- Ticket sessions: orchestrator-driven under full-autonomy grant (no separate parked HITL) +- Agent briefs: Grok/Opus bounded review sidecars for standards/spec; implementation on orchestrator + Opus sidecar +- Review packets: inline standards/spec review in this run; worthy findings fixed +- Local CodeRabbit report: pending +- PR URL: pending + +## Commands + +- Install: `corepack prepare npm@9.8.1 --activate`; `npm ci` +- Typecheck: `npx tsc --noEmit -p tsconfig.json`; `npx tsc --noEmit -p examples/tsconfig.json` +- Test: `npm test -- --runInBand` (1076 passed) +- Build: `npm run lint && npm run build && npm run build:examples && npm run pack:verify` +- Visual verification: not applicable + +## Ticket Ledger + +| Issue | Type | Status | Review thread | Fixes needed | Verified | +| --- | --- | --- | --- | --- | --- | +| #155 Close stale issues | AFK | closed | n/a (gh-only) | none | yes | +| #156 Kill-list ADR | AFK | implemented | standards/spec | none after ADR+changelog | yes | +| #157 Archive runs | AFK | implemented | standards/spec | README index | yes | +| #158 NIP-42 README | AFK | implemented | standards/spec | fixed wrong export names + structure | yes | +| #159 ZapClient deprecate | AFK | implemented | standards/spec | none | yes | +| #160 SimpleNIP46 deprecate | AFK | implemented | standards/spec | examples note + docblock wording | yes | +| #161 NIP-46 validators | AFK | implemented | standards/spec | changelog Security for curve-order | yes | +| #162 Type allowlists | AFK | implemented | standards/spec | NIP-11 duplicate export resolved | yes | + +## Parked HITL Slices + +| Issue | Why parked | Blocks | Required human action | Final PR decision | +| --- | --- | --- | --- | --- | +| None | — | — | — | — | + +## Issue Session Ledger + +| Issue | Fixed point | Worker session | Commit | Review result | Checks | +| --- | --- | --- | --- | --- | --- | +| #155 | staging | orchestrator | (gh-only) | n/a | issues closed | +| #156–#162 | staging | orchestrator + Opus implement | pending | standards/spec findings fixed | tsc, lint, 1076 routine | + +## Open Questions + +- None. Owner granted full end-to-end autonomy. + +## Alignment Decisions (locked) + +1. Close `#82`, `#88–91`, `#93–98` as shipped. +2. Keep Compatibility Aliases through 0.x; document 1.0 removals in ADR 0003. +3. `NostrZapClient` canonical; deprecate `ZapClient`. +4. `NostrRemoteSigner*` production; deprecate `SimpleNIP46*` for 1.0. +5. NIP-46 boolean validators → `isValidNip46*`; private-key uses canonical key-validation. +6. Explicit root/web type allowlists including Compatibility Aliases. +7. Archive session dumps; keep ledgers. +8. Out of scope: Tier C splits, alias removals, release to main. + +## Escalations + +- None. diff --git a/examples/nip46/README.md b/examples/nip46/README.md index 5c76a1f1..f7ad3215 100644 --- a/examples/nip46/README.md +++ b/examples/nip46/README.md @@ -16,6 +16,10 @@ See [ARCHITECTURE.md](./ARCHITECTURE.md) for diagrams and a visual explanation o ## Examples +> **Note:** Prefer `NostrRemoteSignerClient` / `NostrRemoteSignerBunker` for production. +> Examples that still use `SimpleNIP46*` are demo/compat only; those classes are +> Compatibility Aliases deprecated for the next major release (ADR 0003). + ### Unified Example (`unified-example.ts`) - RECOMMENDED A single comprehensive example that shows the core functionality: diff --git a/src/entries/index.web.ts b/src/entries/index.web.ts index a8e40753..476b12c9 100644 --- a/src/entries/index.web.ts +++ b/src/entries/index.web.ts @@ -25,7 +25,59 @@ export type { } from "../nip01/nostr"; // Export types -export * from "../types/nostr"; +// Explicit allowlist instead of a star export so the public type surface stays intentional (ADR 0003). +export { + RelayEvent, + RelayStatus, + NostrKind, + WebSocketReadyState, + RelayErrorType, + NIP20Prefix, +} from "../types/nostr"; +export type { + NostrEvent, + EventTemplate, + NostrFilter, + Filter, + Subscription, + RelayReceivedEvent, + RelayEventCallbacks, + RelayEventHandler, + PublishOptions, + PublishResponse, + SubscriptionOptions, + SubscriptionResponse, + FeeSchedule, + RelayFees, + RelayLimitation, + RelayInfo, + RelayInformation, + RelayStats, + RelayGroup, + ReconnectionStrategy, + RelayCapabilities, + MetricsCollectorOptions, + RelayMessageStats, + ProfileMetadata, + RelayDebugOptions, + RelayError, + RelayInterface, + ValidationOptions, + EventOfKind, + MetadataEvent, + TextNoteEvent, + RecommendRelayEvent, + ContactsEvent, + DirectMessageEvent, + DeletionEvent, + RepostEvent, + ReactionEvent, + BadgeAwardEvent, + TaggedEvent, + TagValues, + EventTags, + ParsedOkReason, +} from "../types/nostr"; // Export utilities export { @@ -98,13 +150,7 @@ export { } from "../nip11"; export { fetchRelayInformation as getRelayInfo } from "../nip11"; -// Export NIP-11 types -export type { - RelayInfo, - RelayLimitation, - RelayFees, - FeeSchedule, -} from "../nip11"; +// NIP-11 relay-information types are the canonical `types/nostr` declarations exported above. // Export NIP-44 utilities export { @@ -279,10 +325,7 @@ export { createQuoteTag, parseThreadReferences, } from "../nip10"; -export type { - ThreadPointer, - ThreadReferences, -} from "../nip10"; +export type { ThreadPointer, ThreadReferences } from "../nip10"; // Export signer utilities export { @@ -324,15 +367,12 @@ export type { ZapStats, } from "../nip57"; -export { - NostrZapClient, - ZapClient, -} from "../nip57/client"; +export { NostrZapClient, ZapClient } from "../nip57"; export type { ZapClientOptions, ZapInvoiceResult, ZapFilterOptions, -} from "../nip57/client"; +} from "../nip57"; export { fetchLnurlPayMetadata, @@ -365,10 +405,7 @@ export { getWriteRelays, RELAY_LIST_KIND, } from "../nip65"; -export type { - RelayListEntry, - RelayListEvent, -} from "../nip65"; +export type { RelayListEntry, RelayListEvent } from "../nip65"; // NIP-66: Relay Discovery and Liveness Monitoring export { diff --git a/src/index.ts b/src/index.ts index 19b7750e..ac334a13 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,59 @@ export type { } from "./nip01/nostr"; // Export types -export * from "./types/nostr"; +// Explicit allowlist instead of a star export so the public type surface stays intentional (ADR 0003). +export { + RelayEvent, + RelayStatus, + NostrKind, + WebSocketReadyState, + RelayErrorType, + NIP20Prefix, +} from "./types/nostr"; +export type { + NostrEvent, + EventTemplate, + NostrFilter, + Filter, + Subscription, + RelayReceivedEvent, + RelayEventCallbacks, + RelayEventHandler, + PublishOptions, + PublishResponse, + SubscriptionOptions, + SubscriptionResponse, + FeeSchedule, + RelayFees, + RelayLimitation, + RelayInfo, + RelayInformation, + RelayStats, + RelayGroup, + ReconnectionStrategy, + RelayCapabilities, + MetricsCollectorOptions, + RelayMessageStats, + ProfileMetadata, + RelayDebugOptions, + RelayError, + RelayInterface, + ValidationOptions, + EventOfKind, + MetadataEvent, + TextNoteEvent, + RecommendRelayEvent, + ContactsEvent, + DirectMessageEvent, + DeletionEvent, + RepostEvent, + ReactionEvent, + BadgeAwardEvent, + TaggedEvent, + TagValues, + EventTags, + ParsedOkReason, +} from "./types/nostr"; export type { NostrClientToServerEventMessage, NostrServerToClientEventMessage, @@ -117,13 +169,7 @@ export { } from "./nip11"; export { fetchRelayInformation as getRelayInfo } from "./nip11"; -// Export NIP-11 types -export type { - RelayInfo, - RelayLimitation, - RelayFees, - FeeSchedule, -} from "./nip11"; +// NIP-11 relay-information types are the canonical `types/nostr` declarations exported above. // Export NIP-44 utilities export { @@ -293,10 +339,7 @@ export { createQuoteTag, parseThreadReferences, } from "./nip10"; -export type { - ThreadPointer, - ThreadReferences, -} from "./nip10"; +export type { ThreadPointer, ThreadReferences } from "./nip10"; // Export signer utilities export { @@ -352,15 +395,12 @@ export type { ZapStats, } from "./nip57"; -export { - NostrZapClient, - ZapClient, -} from "./nip57/client"; +export { NostrZapClient, ZapClient } from "./nip57"; export type { ZapClientOptions, ZapInvoiceResult, ZapFilterOptions, -} from "./nip57/client"; +} from "./nip57"; export { fetchLnurlPayMetadata, @@ -416,10 +456,7 @@ export { getWriteRelays, RELAY_LIST_KIND, } from "./nip65"; -export type { - RelayListEntry, - RelayListEvent, -} from "./nip65"; +export type { RelayListEntry, RelayListEvent } from "./nip65"; // NIP-56 reporting export { diff --git a/src/nip42/README.md b/src/nip42/README.md new file mode 100644 index 00000000..49cfbca0 --- /dev/null +++ b/src/nip42/README.md @@ -0,0 +1,66 @@ +# NIP-42: Authentication of clients to relays + +Helpers for [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) Relay authentication using kind `22242` auth Events. + +## Overview + +NIP-42 lets a Relay challenge a client to prove control of a pubkey by signing an auth Event. This module builds, signs, and validates those Events so clients can respond to `AUTH` / `auth-required` flows. + +## Key Features + +- Build unsigned auth Event Templates with sanitized `relay` and `challenge` tags +- Sign auth Events with a private key +- Validate auth Event structure, signatures, Relay URL, and challenge +- Parse `auth-required` closed reasons from NIP-20-style prefixes + +## Basic Usage + +```typescript +import { + AUTH_EVENT_KIND, + createAuthEventTemplate, + createSignedAuthEvent, + isAuthEvent, + validateAuthEvent, + parseAuthRequiredReason, +} from "snstr"; + +const challenge = "relay-issued-challenge"; +const relayUrl = "wss://relay.example.com"; + +const authEvent = await createSignedAuthEvent( + challenge, + relayUrl, + privateKey, +); + +const isValid = await validateAuthEvent(authEvent, { + challenge, + relayUrl, + validateSignatures: true, +}); +``` + +## Public exports + +| Export | Purpose | +| --- | --- | +| `AUTH_EVENT_KIND` | Kind `22242` for client authentication Events | +| `createAuthEventTemplate` | Build an Event Template with `relay` and `challenge` tags | +| `createSignedAuthEvent` | Sign an auth Event for a given Relay challenge | +| `isAuthEvent` | Structural check for a kind-22242 auth Event | +| `validateAuthEvent` | Async validation against challenge, Relay URL, and optional drift | +| `parseAuthRequiredReason` | Extract text after an `auth-required:` closed reason prefix | +| `NIP42ValidationOptions` | Options for signature checks, challenge, Relay URL, and timestamp drift | + +## Implementation Details + +- Challenge and Relay URL values are sanitized before tagging. +- Relay URLs are normalized before they are written into the auth Event. +- These helpers compose with the core Relay auth callbacks on the `Nostr` and `Relay` Public Facades; they do not open connections themselves. + +## Security Considerations + +- Always validate signatures when accepting auth Events from untrusted sources. +- Bound timestamp drift (`maxTimestampDrift`) to limit replay of old challenges. +- Treat challenge strings as untrusted input; they are sanitized to tag-element limits before use. diff --git a/src/nip46/README.md b/src/nip46/README.md index 52eeb1b8..e1949bce 100644 --- a/src/nip46/README.md +++ b/src/nip46/README.md @@ -24,9 +24,9 @@ NIP-46 defines a protocol for remote signing of Nostr events, allowing applicati - **Connection String Security**: URL validation with injection prevention ### 🏗️ **Dual Implementation Architecture** -- **Full-Featured Implementation**: Complete NIP-46 protocol with all advanced features -- **Simplified Implementation**: Lightweight version for basic use cases and learning -- **Flexible API Design**: Choose the complexity level that fits your needs +- **Full-Featured Implementation**: Complete NIP-46 protocol with all advanced features. This is the production Public Facade. +- **Simplified Implementation**: Lightweight demo/compat version, deprecated as a Compatibility Alias and planned for removal in the next major (ADR 0003) +- **Flexible API Design**: Use Full for production; use Simple only for demos, learning, or existing 0.x code ### 🚀 **Production-Ready Features** - **Dual Encryption Support**: Both NIP-04 (AES-CBC) and NIP-44 (ChaCha20+HMAC) @@ -56,7 +56,10 @@ import { - Comprehensive security validation - Production-ready error handling -### Simplified Implementation (Great for Learning) +### Simplified Implementation (Demo/Compat, Deprecated) + +> Deprecated: `SimpleNIP46Client` and `SimpleNIP46Bunker` are demo/compat Compatibility Aliases kept through 0.x and planned for removal in the next major (ADR 0003). New code should use `NostrRemoteSignerClient` / `NostrRemoteSignerBunker`. + ```typescript import { SimpleNIP46Client, @@ -757,22 +760,28 @@ function createSuccessResponse(id: string, result: string): NIP46Response function createErrorResponse(id: string, error: string): NIP46Response // Security validation utilities -class NIP46Validator { - static validatePubkey(pubkey: string): boolean - static validatePrivateKey(privateKey: string): boolean - static validateEventContent(content: string): boolean - static validateRequestPayload(request: NIP46Request): boolean - static validateConnectionString(connectionString: string): boolean - static validateRelayUrl(url: string): boolean - static validatePermission(permission: string): boolean -} - -// Security validation functions: - static validatePrivateKey(privateKey: string, context?: string): void - static validateKeypairForCrypto(keypair: NIP46KeyPair, context?: string): void - static validateBeforeSigning(userKeypair: NIP46KeyPair, eventData: any): void - static validateBeforeEncryption(userKeypair: NIP46KeyPair, thirdPartyPubkey: string, data: string): void -} +// The legacy validate* names for the boolean validators remain exported as +// deprecated Compatibility Aliases (ADR 0003). +function isValidNip46Pubkey(pubkey: string): boolean +function isValidNip46PrivateKey(privateKey: string): boolean +function isValidNip46EventId(eventId: string): boolean +function isValidNip46Signature(signature: string): boolean +function isValidNip46EventContent(content: string): boolean +function isValidNip46RelayUrl(url: string): boolean +function validateRequestPayload(request: NIP46Request): boolean +function validateConnectionString(connectionString: string): boolean +function validatePermission(permission: string): boolean + +// Throwing security helpers (from utils/security.ts — distinct from the +// boolean isValidNip46* validators above) +function validatePrivateKey(privateKey: string, context?: string): void +function validateKeypairForCrypto(keypair: NIP46KeyPair, context?: string): void +function validateBeforeSigning(userKeypair: NIP46KeyPair, eventData: unknown): void +function validateBeforeEncryption( + userKeypair: NIP46KeyPair, + thirdPartyPubkey: string, + data: string, +): void // Rate limiting class NIP46RateLimiter { diff --git a/src/nip46/index.ts b/src/nip46/index.ts index b9193ef6..244543e1 100644 --- a/src/nip46/index.ts +++ b/src/nip46/index.ts @@ -9,7 +9,7 @@ * * Two implementations are provided: * - * 1. NostrRemoteSignerClient/NostrRemoteSignerBunker: + * 1. NostrRemoteSignerClient/NostrRemoteSignerBunker (production Public Facade): * - Full-featured implementation with all NIP-46 capabilities * - Supports auth challenges via URL * - Handles permissions for specific event kinds @@ -17,16 +17,15 @@ * - Connection metadata for better UX * - Secret token support for secure connections * - * 2. SimpleNIP46Client/SimpleNIP46Bunker: - * - Simplified implementation for basic use cases + * 2. SimpleNIP46Client/SimpleNIP46Bunker (demo/compat Compatibility Alias, deprecated for 1.0): + * - Simplified implementation for demos, learning, and 0.x compatibility * - Minimal code with core functionality * - NIP-04 encryption only * - Basic permission handling * - No auth challenges or metadata support - * - Good for learning and simple applications * - * Choose the implementation that best fits your needs based on the complexity - * and security requirements of your application. + * Use the Full implementation for production. The Simple implementation must not grow new + * capabilities; it is deprecated and planned for removal in the next major (ADR 0003). */ // Export types @@ -63,15 +62,15 @@ export { MAX_TAGS_COUNT, MAX_TAG_ELEMENT_LENGTH, // Validation functions - validateEventContent, - validatePubkey, - validateEventId, - validateSignature, - validatePrivateKey, + isValidNip46EventContent, + isValidNip46Pubkey, + isValidNip46EventId, + isValidNip46Signature, + isValidNip46PrivateKey, + isValidNip46RelayUrl, validateRequestPayload, isValidMethod, validateParams, - validateRelayUrl, validatePermission, validateConnectionString, validateAndParseJson, @@ -79,6 +78,13 @@ export { validateTimestamp, // Error handler class SecureErrorHandler, + // Deprecated validator Compatibility Aliases (planned removal in next major, ADR 0003) + validateEventContent, + validatePubkey, + validateEventId, + validateSignature, + validatePrivateKey, + validateRelayUrl, } from "./utils/validator"; // Re-export utility functions from crypto to make the API more convenient diff --git a/src/nip46/internal/wire.ts b/src/nip46/internal/wire.ts index 24335921..8396d1a5 100644 --- a/src/nip46/internal/wire.ts +++ b/src/nip46/internal/wire.ts @@ -7,7 +7,7 @@ import { MAX_CONTENT_SIZE, MAX_ID_LENGTH, MAX_PARAMS_COUNT, - validatePubkey, + isValidNip46Pubkey, } from "../utils/validator"; export const NIP46_EVENT_KIND = 24133; @@ -111,7 +111,7 @@ export class NIP46Wire { const pubkey = payload.pubkey; return ( pubkey === undefined || - (typeof pubkey === "string" && validatePubkey(pubkey)) + (typeof pubkey === "string" && isValidNip46Pubkey(pubkey)) ); } diff --git a/src/nip46/simple-bunker.ts b/src/nip46/simple-bunker.ts index 3c063c0d..acb1263d 100644 --- a/src/nip46/simple-bunker.ts +++ b/src/nip46/simple-bunker.ts @@ -35,6 +35,9 @@ interface ClientSession { * * This class implements the signer-side of the NIP-46 Remote Signing protocol. * It is designed to be lightweight and easy to use. + * + * @deprecated Use NostrRemoteSignerBunker instead. Retained for demo/compat use only as a + * Compatibility Alias through 0.x; planned removal in the next major (ADR 0003). */ export class SimpleNIP46Bunker { private readonly engine: NIP46BunkerEngine; diff --git a/src/nip46/simple-client.ts b/src/nip46/simple-client.ts index 410f5844..bc5fb607 100644 --- a/src/nip46/simple-client.ts +++ b/src/nip46/simple-client.ts @@ -14,7 +14,12 @@ import { SimpleNIP46ClientOptions, } from "./types"; -/** Lightweight public facade over the canonical NIP-46 client engine. */ +/** + * Lightweight demo/compat wrapper over the canonical NIP-46 client engine. + * + * @deprecated Use NostrRemoteSignerClient instead. Retained for demo/compat use only as a + * Compatibility Alias through 0.x; planned removal in the next major (ADR 0003). + */ export class SimpleNIP46Client { private readonly logger: NIP46DiagnosticLogger; private readonly engine: NIP46ClientEngine; diff --git a/src/nip46/utils/connection.ts b/src/nip46/utils/connection.ts index 423edf12..f03c9d51 100644 --- a/src/nip46/utils/connection.ts +++ b/src/nip46/utils/connection.ts @@ -22,8 +22,8 @@ export function buildConnectionString( import { NIP46ConnectionError, NIP46SecurityError } from "../types"; import type { NIP46ConnectionInfo, NIP46Metadata } from "../types"; import { - validatePubkey, - validateRelayUrl, + isValidNip46Pubkey, + isValidNip46RelayUrl, validatePermission, sanitizeString, } from "./validator"; @@ -118,7 +118,7 @@ export function parseConnectionString(str: string): NIP46ConnectionInfo { : afterProtocol.slice(0, delimiterStart); // Validate pubkey using secure validator - if (!validatePubkey(pubkey)) { + if (!isValidNip46Pubkey(pubkey)) { throw new NIP46ConnectionError( "Invalid signer public key in connection string", ); @@ -131,7 +131,7 @@ export function parseConnectionString(str: string): NIP46ConnectionInfo { const allRelays = url.searchParams.getAll("relay"); const relays = allRelays.filter((relay) => { // Use enhanced relay validation - return validateRelayUrl(relay); + return isValidNip46RelayUrl(relay); }); // Fail fast if all relay URLs are invalid diff --git a/src/nip46/utils/validator.ts b/src/nip46/utils/validator.ts index 32b52cd1..c59f3485 100644 --- a/src/nip46/utils/validator.ts +++ b/src/nip46/utils/validator.ts @@ -1,6 +1,7 @@ import { NIP46Request, NIP46Method } from "../types"; import { Logger, LogLevel } from "../../utils/logger"; import { isHexOfLength, utf8ByteLength } from "../../utils/wire-validation"; +import { isValidPrivateKey } from "../../utils/key-validation"; /** * Enhanced validation utilities for NIP-46 security @@ -17,7 +18,7 @@ export const MAX_TAG_ELEMENT_LENGTH = 2048; // Maximum length per tag element /** * Validate event content size and structure */ -export function validateEventContent(content: string): boolean { +export function isValidNip46EventContent(content: string): boolean { if (!content || typeof content !== "string") { return false; } @@ -37,7 +38,7 @@ export function validateEventContent(content: string): boolean { const errorMessage = error instanceof Error ? error.message : "Unknown JSON parsing error"; SecureErrorHandler.logSecurityEvent( - "JSON parsing failed in validateEventContent", + "JSON parsing failed in isValidNip46EventContent", { error: SecureErrorHandler.sanitizeError( new Error(errorMessage), @@ -135,29 +136,31 @@ function validateTags(tags: unknown): boolean { /** * Validate public key format (strict hex validation) */ -export function validatePubkey(pubkey: string): boolean { +export function isValidNip46Pubkey(pubkey: string): boolean { return isHexOfLength(pubkey, 64); } /** * Validate event ID format */ -export function validateEventId(eventId: string): boolean { +export function isValidNip46EventId(eventId: string): boolean { return isHexOfLength(eventId, 64); } /** * Validate signature format */ -export function validateSignature(signature: string): boolean { +export function isValidNip46Signature(signature: string): boolean { return isHexOfLength(signature, 128); } /** - * Validate private key format (for internal use) + * Validate private key material using the canonical secp256k1 key policy. + * + * Hex shape alone is insufficient: keys outside the curve order are rejected. */ -export function validatePrivateKey(privateKey: string): boolean { - return isHexOfLength(privateKey, 64); +export function isValidNip46PrivateKey(privateKey: string): boolean { + return isValidPrivateKey(privateKey); } /** @@ -193,7 +196,7 @@ export function validateRequestPayload(request: NIP46Request): boolean { } // Validate optional pubkey if present - if (request.pubkey && !validatePubkey(request.pubkey)) { + if (request.pubkey && !isValidNip46Pubkey(request.pubkey)) { return false; } @@ -238,7 +241,7 @@ export function validateParams(params: string[]): boolean { /** * Validate relay URL format */ -export function validateRelayUrl(url: string): boolean { +export function isValidNip46RelayUrl(url: string): boolean { if (!url || typeof url !== "string") { return false; } @@ -267,7 +270,7 @@ export function validateRelayUrl(url: string): boolean { const errorMessage = error instanceof Error ? error.message : "Unknown URL parsing error"; SecureErrorHandler.logSecurityEvent( - "URL parsing failed in validateRelayUrl", + "URL parsing failed in isValidNip46RelayUrl", { error: SecureErrorHandler.sanitizeError( new Error(errorMessage), @@ -338,14 +341,14 @@ export function validateConnectionString(connectionString: string): boolean { // Extract and validate pubkey from hostname const pubkey = url.hostname; - if (!validatePubkey(pubkey)) { + if (!isValidNip46Pubkey(pubkey)) { return false; } // Validate relay URLs if present const relays = url.searchParams.getAll("relay"); for (const relay of relays) { - if (!validateRelayUrl(relay)) { + if (!isValidNip46RelayUrl(relay)) { return false; } } @@ -443,6 +446,24 @@ export function validateTimestamp( return age >= 0 && age <= maxAgeSeconds; } +/** @deprecated Use isValidNip46EventContent. Compatibility Alias until next major (ADR 0003). */ +export const validateEventContent = isValidNip46EventContent; + +/** @deprecated Use isValidNip46Pubkey. Compatibility Alias until next major (ADR 0003). */ +export const validatePubkey = isValidNip46Pubkey; + +/** @deprecated Use isValidNip46EventId. Compatibility Alias until next major (ADR 0003). */ +export const validateEventId = isValidNip46EventId; + +/** @deprecated Use isValidNip46Signature. Compatibility Alias until next major (ADR 0003). */ +export const validateSignature = isValidNip46Signature; + +/** @deprecated Use isValidNip46PrivateKey. Compatibility Alias until next major (ADR 0003). */ +export const validatePrivateKey = isValidNip46PrivateKey; + +/** @deprecated Use isValidNip46RelayUrl. Compatibility Alias until next major (ADR 0003). */ +export const validateRelayUrl = isValidNip46RelayUrl; + /** * Secure error handler to prevent information disclosure */ diff --git a/src/nip57/README.md b/src/nip57/README.md index 5e756b0b..bfea78aa 100644 --- a/src/nip57/README.md +++ b/src/nip57/README.md @@ -118,6 +118,8 @@ if (calculatedHash !== parsedInvoice.descriptionHash) { The `NostrZapClient` provides a simple, high-level interface for working with zaps: +`NostrZapClient` is the canonical Public Facade for NIP-57; `ZapClient` remains exported only as a Compatibility Alias and is deprecated for removal in the next major release (ADR 0003). + ```typescript import { Nostr, NostrZapClient } from 'snstr'; diff --git a/src/nip57/client.ts b/src/nip57/client.ts index 2919ac05..1c3ceff1 100644 --- a/src/nip57/client.ts +++ b/src/nip57/client.ts @@ -754,7 +754,12 @@ class ZapClientCore { } } -/** Comprehensive public NIP-57 facade retained for 0.x compatibility. */ +/** + * Canonical Public Facade for NIP-57 Lightning Zaps. + * + * Prefer this class over `ZapClient`, which is retained only as a + * Compatibility Alias through the 0.x line. + */ export class NostrZapClient { private core: ZapClientCore; @@ -867,6 +872,8 @@ export class NostrZapClient { /** * Client for working with NIP-57 Zaps + * + * @deprecated Use NostrZapClient instead. Compatibility Alias retained through 0.x; planned removal in the next major (ADR 0003). */ export class ZapClient { private core: ZapClientCore; diff --git a/src/nip57/index.ts b/src/nip57/index.ts index 744ad046..ede73de7 100644 --- a/src/nip57/index.ts +++ b/src/nip57/index.ts @@ -17,7 +17,7 @@ import type { DiagnosticLogger } from "../utils/logger"; export type { LnurlSuccessAction, LnurlInvoiceResponse } from "./types"; // Export values and types from client.ts -export { NostrZapClient } from "./client"; +export { NostrZapClient, ZapClient } from "./client"; export type { ZapClientOptions, ZapInvoiceResult, diff --git a/tests/nip46/validator-unit.test.ts b/tests/nip46/validator-unit.test.ts index 5f776527..81ca8b98 100644 --- a/tests/nip46/validator-unit.test.ts +++ b/tests/nip46/validator-unit.test.ts @@ -1,5 +1,11 @@ import { validateRequestPayload, + isValidNip46EventContent, + isValidNip46Pubkey, + isValidNip46PrivateKey, + isValidNip46Signature, + isValidNip46EventId, + isValidNip46RelayUrl, validateEventContent, validatePubkey, validatePrivateKey as validatePrivateKeyFormat, @@ -67,7 +73,7 @@ describe("NIP-46 Validator Unit Tests", () => { }); }); - describe("validateEventContent", () => { + describe("isValidNip46EventContent", () => { test("validates valid event content", () => { const validEvent = { kind: 1, @@ -75,89 +81,115 @@ describe("NIP-46 Validator Unit Tests", () => { created_at: Math.floor(Date.now() / 1000), tags: [], }; + expect(isValidNip46EventContent(JSON.stringify(validEvent))).toBe(true); expect(validateEventContent(JSON.stringify(validEvent))).toBe(true); }); test("rejects invalid content", () => { - expect(validateEventContent("")).toBe(false); + expect(isValidNip46EventContent("")).toBe(false); + expect(isValidNip46EventContent("invalid json")).toBe(false); + expect(isValidNip46EventContent("x".repeat(70000))).toBe(false); expect(validateEventContent("invalid json")).toBe(false); - expect(validateEventContent("x".repeat(70000))).toBe(false); }); }); - describe("validatePubkey", () => { + describe("isValidNip46Pubkey", () => { test("validates valid pubkeys", () => { + expect(isValidNip46Pubkey(validKeypair.publicKey)).toBe(true); + expect(isValidNip46Pubkey("a".repeat(64))).toBe(true); + expect(isValidNip46Pubkey("A".repeat(64))).toBe(true); expect(validatePubkey(validKeypair.publicKey)).toBe(true); - expect(validatePubkey("a".repeat(64))).toBe(true); - expect(validatePubkey("A".repeat(64))).toBe(true); }); test("rejects invalid pubkeys", () => { - expect(validatePubkey("")).toBe(false); + expect(isValidNip46Pubkey("")).toBe(false); + expect(isValidNip46Pubkey("invalid")).toBe(false); + expect(isValidNip46Pubkey("g".repeat(64))).toBe(false); + expect(isValidNip46Pubkey(null as unknown as string)).toBe(false); expect(validatePubkey("invalid")).toBe(false); - expect(validatePubkey("g".repeat(64))).toBe(false); - expect(validatePubkey(null as unknown as string)).toBe(false); }); }); - describe("validatePrivateKeyFormat", () => { + describe("isValidNip46PrivateKey", () => { test("validates valid private keys", () => { + expect(isValidNip46PrivateKey(validKeypair.privateKey)).toBe(true); + expect(isValidNip46PrivateKey("b".repeat(64))).toBe(true); expect(validatePrivateKeyFormat(validKeypair.privateKey)).toBe(true); - expect(validatePrivateKeyFormat("b".repeat(64))).toBe(true); }); test("rejects invalid private keys", () => { - expect(validatePrivateKeyFormat("")).toBe(false); + expect(isValidNip46PrivateKey("")).toBe(false); + expect(isValidNip46PrivateKey("invalid")).toBe(false); + expect(isValidNip46PrivateKey("z".repeat(64))).toBe(false); expect(validatePrivateKeyFormat("invalid")).toBe(false); - expect(validatePrivateKeyFormat("z".repeat(64))).toBe(false); + }); + + test("rejects hex keys outside the secp256k1 curve order", () => { + expect(isValidNip46PrivateKey("f".repeat(64))).toBe(false); + expect(isValidNip46PrivateKey("0".repeat(64))).toBe(false); }); }); - describe("validateSignature", () => { + describe("isValidNip46Signature", () => { test("validates valid signatures", () => { + expect(isValidNip46Signature("c".repeat(128))).toBe(true); + expect(isValidNip46Signature("C".repeat(128))).toBe(true); expect(validateSignature("c".repeat(128))).toBe(true); - expect(validateSignature("C".repeat(128))).toBe(true); }); test("rejects invalid signatures", () => { - expect(validateSignature("")).toBe(false); + expect(isValidNip46Signature("")).toBe(false); + expect(isValidNip46Signature("invalid")).toBe(false); + expect(isValidNip46Signature("z".repeat(128))).toBe(false); expect(validateSignature("invalid")).toBe(false); - expect(validateSignature("z".repeat(128))).toBe(false); }); test("validates signatures correctly with various formats", () => { - expect(validateSignature("a".repeat(128))).toBe(true); - expect(validateSignature("A".repeat(128))).toBe(true); - expect(validateSignature("1234567890abcdef".repeat(8))).toBe(true); + expect(isValidNip46Signature("a".repeat(128))).toBe(true); + expect(isValidNip46Signature("A".repeat(128))).toBe(true); + expect(isValidNip46Signature("1234567890abcdef".repeat(8))).toBe(true); - expect(validateSignature("")).toBe(false); - expect(validateSignature("a".repeat(127))).toBe(false); - expect(validateSignature("a".repeat(129))).toBe(false); - expect(validateSignature("xyz" + "a".repeat(125))).toBe(false); + expect(isValidNip46Signature("")).toBe(false); + expect(isValidNip46Signature("a".repeat(127))).toBe(false); + expect(isValidNip46Signature("a".repeat(129))).toBe(false); + expect(isValidNip46Signature("xyz" + "a".repeat(125))).toBe(false); }); }); - describe("validateEventId", () => { + describe("isValidNip46EventId", () => { test("validates valid event IDs", () => { + expect(isValidNip46EventId("d".repeat(64))).toBe(true); + expect(isValidNip46EventId("D".repeat(64))).toBe(true); expect(validateEventId("d".repeat(64))).toBe(true); - expect(validateEventId("D".repeat(64))).toBe(true); }); test("rejects invalid event IDs", () => { - expect(validateEventId("")).toBe(false); + expect(isValidNip46EventId("")).toBe(false); + expect(isValidNip46EventId("invalid")).toBe(false); + expect(isValidNip46EventId("z".repeat(64))).toBe(false); expect(validateEventId("invalid")).toBe(false); - expect(validateEventId("z".repeat(64))).toBe(false); }); test("validates event IDs correctly with various formats", () => { - expect(validateEventId("a".repeat(64))).toBe(true); - expect(validateEventId("A".repeat(64))).toBe(true); - expect(validateEventId("1234567890abcdef".repeat(4))).toBe(true); + expect(isValidNip46EventId("a".repeat(64))).toBe(true); + expect(isValidNip46EventId("A".repeat(64))).toBe(true); + expect(isValidNip46EventId("1234567890abcdef".repeat(4))).toBe(true); + + expect(isValidNip46EventId("")).toBe(false); + expect(isValidNip46EventId("a".repeat(63))).toBe(false); + expect(isValidNip46EventId("a".repeat(65))).toBe(false); + expect(isValidNip46EventId("xyz" + "a".repeat(61))).toBe(false); + }); + }); - expect(validateEventId("")).toBe(false); - expect(validateEventId("a".repeat(63))).toBe(false); - expect(validateEventId("a".repeat(65))).toBe(false); - expect(validateEventId("xyz" + "a".repeat(61))).toBe(false); + describe("deprecated validator Compatibility Aliases", () => { + test("aliases resolve to the canonical isValidNip46* implementations", () => { + expect(validateEventContent).toBe(isValidNip46EventContent); + expect(validatePubkey).toBe(isValidNip46Pubkey); + expect(validateEventId).toBe(isValidNip46EventId); + expect(validateSignature).toBe(isValidNip46Signature); + expect(validatePrivateKeyFormat).toBe(isValidNip46PrivateKey); + expect(validateRelayUrl).toBe(isValidNip46RelayUrl); }); }); @@ -188,15 +220,17 @@ describe("NIP-46 Validator Unit Tests", () => { }); }); - describe("validateRelayUrl", () => { + describe("isValidNip46RelayUrl", () => { test("validates valid relay URLs", () => { + expect(isValidNip46RelayUrl("ws://localhost:3792")).toBe(true); + expect(isValidNip46RelayUrl("wss://relay.example.com")).toBe(true); expect(validateRelayUrl("ws://localhost:3792")).toBe(true); - expect(validateRelayUrl("wss://relay.example.com")).toBe(true); }); test("rejects invalid URLs", () => { - expect(validateRelayUrl("")).toBe(false); - expect(validateRelayUrl("http://example.com")).toBe(false); + expect(isValidNip46RelayUrl("")).toBe(false); + expect(isValidNip46RelayUrl("http://example.com")).toBe(false); + expect(isValidNip46RelayUrl("invalid")).toBe(false); expect(validateRelayUrl("invalid")).toBe(false); }); }); From a547f1ad617e26f92d7fcd20627c28e48e874ff4 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Tue, 28 Jul 2026 12:17:55 -0500 Subject: [PATCH 2/3] docs: record Tier A+B CodeRabbit fallback and PR status Co-authored-by: Cursor --- docs/agents/runs/archive/tier-ab-coderabbit-local.md | 8 ++++++++ docs/agents/runs/feature-cleanup-tier-ab-ledger.md | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 docs/agents/runs/archive/tier-ab-coderabbit-local.md diff --git a/docs/agents/runs/archive/tier-ab-coderabbit-local.md b/docs/agents/runs/archive/tier-ab-coderabbit-local.md new file mode 100644 index 00000000..1145bcdc --- /dev/null +++ b/docs/agents/runs/archive/tier-ab-coderabbit-local.md @@ -0,0 +1,8 @@ +# CodeRabbit local gate — Tier A+B + +- Command: `coderabbit review --agent --type all --base staging` +- Result: unavailable / silent after >4 minutes with no stdout (process terminated) +- Fallback: fresh standards + spec code-review against staging (Feature Dev unavailable-fallback) +- Fallback outcome: worthy findings fixed (NIP-42 README accuracy, NIP-46 README API block, curve-order CHANGELOG Security entry, SimpleNIP46 docblock wording, examples note) +- Ignored: remaining boolean peers not renamed (`validateRequestPayload`, etc.) — judgement call; ADR kill-list covers colliding names; further rename deferred +- Checks after fixes: tsc clean, lint clean, routine Jest 1076 passed, pack/commands/package-manager verify OK diff --git a/docs/agents/runs/feature-cleanup-tier-ab-ledger.md b/docs/agents/runs/feature-cleanup-tier-ab-ledger.md index 60d5152b..4d6422d5 100644 --- a/docs/agents/runs/feature-cleanup-tier-ab-ledger.md +++ b/docs/agents/runs/feature-cleanup-tier-ab-ledger.md @@ -9,7 +9,7 @@ - Feature branch: `feature/cleanup-tier-ab-api-surface` - Human owner: plebdev - Started: 2026-07-28 -- Current status: implementation + review fixes complete; preparing commits, CodeRabbit, staging PR +- Current status: non-draft PR #163 open into staging; local CodeRabbit unavailable with fallback recorded - Skill setup status: present and verified ## Goal @@ -26,8 +26,8 @@ Complete Tier A and Tier B cleanup end to end: close stale shipped issues, publi - Ticket sessions: orchestrator-driven under full-autonomy grant (no separate parked HITL) - Agent briefs: Grok/Opus bounded review sidecars for standards/spec; implementation on orchestrator + Opus sidecar - Review packets: inline standards/spec review in this run; worthy findings fixed -- Local CodeRabbit report: pending -- PR URL: pending +- Local CodeRabbit report: `docs/agents/runs/archive/tier-ab-coderabbit-local.md` (unavailable fallback) +- PR URL: https://github.com/AustinKelsay/snstr/pull/163 ## Commands @@ -61,7 +61,7 @@ Complete Tier A and Tier B cleanup end to end: close stale shipped issues, publi | Issue | Fixed point | Worker session | Commit | Review result | Checks | | --- | --- | --- | --- | --- | --- | | #155 | staging | orchestrator | (gh-only) | n/a | issues closed | -| #156–#162 | staging | orchestrator + Opus implement | pending | standards/spec findings fixed | tsc, lint, 1076 routine | +| #156–#162 | staging | orchestrator + Opus implement | `2195805` | standards/spec findings fixed | tsc, lint, 1076 routine, pack/commands verify | ## Open Questions From a74838d770d917815152de04c5540dd289683007 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Tue, 28 Jul 2026 12:44:40 -0500 Subject: [PATCH 3/3] docs: polish NIP-42/46 export docs before staging merge Clarify validateAuthEvent throw semantics, remove unexported throwing helpers from the NIP-46 public API reference, and drop the obsolete NIP46Validator test inventory label. Co-authored-by: Cursor --- src/nip42/README.md | 5 +++-- src/nip46/README.md | 14 +++----------- tests/nip46/README.md | 2 +- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/nip42/README.md b/src/nip42/README.md index 49cfbca0..31d4b097 100644 --- a/src/nip42/README.md +++ b/src/nip42/README.md @@ -34,7 +34,8 @@ const authEvent = await createSignedAuthEvent( privateKey, ); -const isValid = await validateAuthEvent(authEvent, { +// validateAuthEvent returns true on success and throws on failure +await validateAuthEvent(authEvent, { challenge, relayUrl, validateSignatures: true, @@ -49,7 +50,7 @@ const isValid = await validateAuthEvent(authEvent, { | `createAuthEventTemplate` | Build an Event Template with `relay` and `challenge` tags | | `createSignedAuthEvent` | Sign an auth Event for a given Relay challenge | | `isAuthEvent` | Structural check for a kind-22242 auth Event | -| `validateAuthEvent` | Async validation against challenge, Relay URL, and optional drift | +| `validateAuthEvent` | Async validation; returns `true` or throws on mismatch/invalid structure | | `parseAuthRequiredReason` | Extract text after an `auth-required:` closed reason prefix | | `NIP42ValidationOptions` | Options for signature checks, challenge, Relay URL, and timestamp drift | diff --git a/src/nip46/README.md b/src/nip46/README.md index e1949bce..0bf5ff43 100644 --- a/src/nip46/README.md +++ b/src/nip46/README.md @@ -771,17 +771,9 @@ function isValidNip46RelayUrl(url: string): boolean function validateRequestPayload(request: NIP46Request): boolean function validateConnectionString(connectionString: string): boolean function validatePermission(permission: string): boolean - -// Throwing security helpers (from utils/security.ts — distinct from the -// boolean isValidNip46* validators above) -function validatePrivateKey(privateKey: string, context?: string): void -function validateKeypairForCrypto(keypair: NIP46KeyPair, context?: string): void -function validateBeforeSigning(userKeypair: NIP46KeyPair, eventData: unknown): void -function validateBeforeEncryption( - userKeypair: NIP46KeyPair, - thirdPartyPubkey: string, - data: string, -): void +// Deprecated Compatibility Aliases (same boolean contracts as isValidNip46*): +// validateEventContent, validatePubkey, validateEventId, validateSignature, +// validatePrivateKey, validateRelayUrl // Rate limiting class NIP46RateLimiter { diff --git a/tests/nip46/README.md b/tests/nip46/README.md index 54e4fa19..42fe40d1 100644 --- a/tests/nip46/README.md +++ b/tests/nip46/README.md @@ -35,7 +35,7 @@ The test suite is organized into **7 focused test files** that provide complete #### **`validator-unit.test.ts`** (565 lines) **Input validation and security hardening**: -- ✅ **NIP46Validator Methods** - All validation functions (pubkeys, signatures, events, etc.) +- ✅ **Validator helpers** - Boolean `isValidNip46*` checks plus deprecated `validate*` aliases - ✅ **Security Error Handling** - Secure error responses and timing attack prevention - ✅ **Request ID Generation** - Cryptographically secure ID generation - ✅ **Private Key Security** - Key validation and secure handling