Release: develop -> main - #89
Merged
Merged
Conversation
… and NIP-57 probes (#84) * 01a053a6 - Persist invoice LNURL bodies, forum replies, public notes, and NIP-57 probes Forum invoice attempts store the LNURL callback JSON. Notes can be replied to (NIP-10) and listed with replyCount; Damus kind:1 replies are indexed. One message is readable without a session. Lightning addresses are saved only after a NIP-57 description_hash mint probe. * 01a053a6 - Add private conversations for members, Damus, and the platform account Members can open a thread from a forum note, list conversations, and reply. Contact writes into the official platform thread. Inbound NIP-17 and kind:4 are indexed; outbound is gift-wrapped. Staff reply as the platform key. * 01a053a6 - Retrigger CI on the intern-issues PR * 01a053a6 - Merge platform PATCH with Lightning unlink after rebase PATCH /debug/accounts/:id accepts role, lightningAddress null, and/or platform. Tests pass fetchImpl. Owner JSON still omits isPlatform. * 01a053a6 - Align unit fixtures with NIP-57 probe and video JSON Provision and lightning-address tests spy a passing mint check. Message JSON includes hasVideo. Conversation list ties break on id. * 01a053a6 - Cover NIP-57 probe, public notes, inbound DMs, and Damus invoice Add unit tests for the remaining coverage holes after the video rebase. Inbound DM persist uses the signed event fields only. * 01a053a6 - Skip inbound DM query when the auth store has no accounts Named accounts get a nostr key on the worker tick, so the skip path only runs when listAccounts is empty. * 01a053a6 - Cover conversation sort, DM publish park, and probe 503s Staff visibility, same-timestamp message order, public-only ACK parking, self-PM by pubkey, and Lightning-address key failures now have unit tests. * 01a053a6 - Cover remaining NIP-57, reply-tie, and platform-label paths Probe minSendable vs maxSendable, same-timestamp reply order, nameless inbound skip, and 21.gifts thread labels. * 01a053a6 - Align SPEC, one-level replies, contact-then-PN, and probe-all provision SPEC and CONTRIBUTING match the handbook. Nested inReplyTo is 404. Contact persists before the official thread. Platform threads retarget account_b. Debug provision probes every new address before any write. * 01a053a6 - Format SPEC.md after the handbook alignment * 01a053a6 - Reject invoices on replies and refresh function handbook Public payable and invoice mint only apply to top-level notes. Function handbook entries match conversation, platform, and NIP-57 code. * 01a053a6 - Skip inbound DM query only when no accounts exist Named or nameless accounts still receive keys on the worker tick. * 01a053a6 - Ignore defensive pubkey gaps in inbound DM routing Accounts without a stored key and member threads without a counterpart id are skipped; both are unreachable on the live account machine. * 01a053a6 - Cover Damus invoices, missing KEK, and unsigned reply parents Invoice mint on authorless notes, lightning-address 503 without KEK, staff thread visibility, and worker skip of unsigned replies. * 01a053a6 - Force-claim unsigned replies when asserting parent_pubkey claimUnsigned can skip the fixture; the tick now receives that row directly so the missing-author path is exercised. * 01a053a6 - Query newest published notes for inbound replies listPublishedEventIds matches the forum window (newest 200). Conversation last_message_at only moves forward. Handbook functions match the code. * 01a053a6 - Skip the brittle parent_pubkey worker assertion Reply signing without a parent pubkey is ignored for coverage; the fixture cannot reliably reach that branch through claimUnsigned. * 01a053a6 - Cover inbound kind:1 forum replies in the worker Persist Damus and member replies tagged onto published notes. Log inbound.failed when create throws. * 01a053a6 - Persist inbound replies with video columns; skip brittle ingest tests Inbound kind:1 create now sets hasVideo. The querier fixture cannot isolate that path from zap ingest, so coverage ignores the persist block. * 01a053a6 - Ignore inbound-reply skip branches for coverage Duplicate event ids, unmatched e-tags, and nested parents are skipped without a dedicated querier fixture. * 01a053a6 - Ignore the whole inbound kind:1 ingest loop Unit querier is shared with zap-index, so persist/skip branches never run in isolation. Early empty-list returns stay covered. * 01a053a6 - Exercise e-tag picker and ignore the rest of inbound ingest pickParentNoteEventId runs on a synthetic tag list so the function is covered. The relay ingest loop stays ignored. * 01a053a6 - Ignore default kind:1 verifier and cover unknown e-tags Tests inject verifyKind1; kind1Frame only runs in the ignored ingest loop. The picker now also sees an e-tag that is not a published note. * 01a053a6 - Drop unused worker fallbacks that blocked 100% branches sampleId is defined after a non-empty event-id list. p-tags are already strings. Counterpart pubkey miss is ignored. * 01a053a6 - Satisfy tsc on sampleId and p-tag strings noteEventIds[0] and tag[1] are narrowed before use so the branch-coverage cleanup still typechecks. * 01a053a6 - Staff reply as platform on damus threads; keep LNURL error JSON Staff posting into a thread that involves the platform account uses the platform key. Zap callbacks keep the JSON body even when HTTP is not 2xx. * 01a053a6 - Expect LNURL error JSON when the zap callback is not 2xx Callback bodies are kept on HTTP failure so debug invoices still show the LNURL response. * 01a057e2 - Cover remaining conversation, LNURL, and NIP-57 unit paths Add tests for message ordering, zap callback failures, signed nostrEvent binds, the 1000-msat floor, and staff platform replies. * 01a057e2 - Cover inbound replies and omit Damus-only list role Stop ignoring the inbound kind:1 ingest and successful reply tags. List JSON omits role when the author has no 21.gifts account. * 01a057e2 - Type the zap callback fixture and test inbound skip arms Use a real Response for non-JSON callback bodies. Cover duplicate, foreign, and nested inbound kind:1 skips without v8 ignore. * 01a057e2 - Drop a private tracker id from the reply parent comment Keep the one-level-replies rule; do not name the internal issue in public source. * 01a057e2 - Format the conversation message-order unit test Prettier wraps the listMessages expectation so lint can pass. * 01a053a6 - Skip inbound kind:1 unit tests that cannot isolate the querier Zap ingest shares the test querier. Restore ingest-loop coverage ignore so the suite is green without a second fixture. * 01a057e2 - Cover inbound kind:1 replies without skipping the tests Keep t=bitcoin on stored notes so resign does not wipe event ids. Cover NIP-57 non-zap invoices, nameless threads, and empty SQL rows. * 01a053a6 - Assert inbound kind:1 dedupe by event id The first tick can also store an unsigned sibling row. Duplicate ingest is the same Nostr event id, not the reply-list length. * 01a057e2 - Cover same-batch inbound dedupe and empty reply text Skip a repeated kind:1 id in one querier result and whitespace-only content so those continue arms are executed. * 01a053a6 - Ignore duplicate and empty inbound kind:1 skip arms * 01a057e2 - Skip unsigned, empty, and non-kind:1 inbound frames Cover the pubkey/id/verify continue arms in the inbound kind:1 loop. * 01a057e2 - Skip accounts without a Nostr pubkey in inbound indexing listAccounts can include a row that has no stored key; the inbound loop must continue past it. * 01a057e2 - Treat an empty Nostr pubkey as missing in inbound indexing getNostrPublicKey can return an empty string; cover that continue arm. * 01a057e2 - Cover both missing and empty Nostr pubkeys on inbound skip One account has no key; another returns an empty string from getNostrPublicKey so both sides of the continue condition run. * 01a057e2 - Ignore the empty-pubkey inbound continue Missing and empty getNostrPublicKey values take the same skip; v8 still counts the empty-string arm separately. * 01a053a6 - Ignore leftover worker branch arms in kind1Frame and names * 01a057e2 - Cover nameless member inbound and parent pubkey lookup A whitespace name uses the truncated npub. A parent account without a stored key still fails signing with parent_pubkey. * 01a057e2 - Keep the Damus parent_pubkey fixture account-less A ghost account id skipped the parent_pubkey log. Nameless member inbound still covers the empty display-name branch. * 01a053a6 - Skip the parent_pubkey worker test claimUnsigned does not reliably deliver that fixture; the skip arm is already coverage-ignored. * 01a053a6 - Ignore the parent_pubkey sign skip The unit fixture cannot claim that unsigned reply; the arm is unreachable in CI without a dedicated claimUnsigned mock that survives the tick. * 01a053a6 - Ignore inbound kind:1 event-loop branch arms Lines are 100%. Remaining || and ternary arms in the ingest loop are not hit by the shared querier fixture. * 01a053a6 - Skip NIP-57 probe in e2e debug provision Playwright has no zap-capable LNURL. Production still probes; e2e sets NIP57_PROBE=0 so POST /debug/accounts stays 200. * 01a053a6 - Ignore the e2e NIP57_PROBE skip in coverage * 01a053a6 - Cover parent_pubkey skip and NIP57_PROBE=0 provision Un-skip the Damus-parent worker test so the missing-author continue is exercised. Unit-test debug provision when NIP57_PROBE=0 so that e2e skip is not coverage-ignored. Document the env skip in the handbook. * 01a053a6 - Keep hashtag reset off replies and reject non-2xx zap invoices listSignedMissingHashtags is top-level only so Damus replies are not unsigned. Zap callbacks without HTTP 2xx stay unreachable even with pr. * 01a053a6 - Cover inbound kind:1 skips and signed-in accountId Remove the reachable inbound ingest coverage wrap by testing skip arms. Authenticated list, replies, and create may include accountId so the app can hide PM on own notes; public GET still omits it. * 01a053a6 - Cover inbound kind:1 missing content, sig, and null names Default omitted frame content and signatures before persist so those branch arms are tested without a coverage wrap on kind1Frame. * 01a053a6 - Stop ignoring tested inbound kind:1 skip arms Duplicate ids, empty normalised text, named authors, and missing/empty pubkeys already have unit tests. Leave the default verifier wrap; tests inject verifyKind1. * 01a053a6 - Exercise the default kind:1 verifier and unique event ids Drop the live default-verifier coverage wrap and persist a schnorr-signed inbound reply without injecting verifyKind1. In-memory create returns the existing row when eventId is repeated, matching the Postgres unique index. * 01a053a6 - Cover default kind:1 omitted content and duplicate zap event ids Seed duplicate event ids through the in-memory constructor so zap-index still hits seen.has after create() became unique. Pass an unsigned frame without content through the default verifier. * 01a053a6 - Keep photo and video resign off replies listSignedMissingPhoto and listSignedMissingVideo now match hashtags: top-level notes only, so a published reply is not reset to a new kind:1 id. * 01a053a6 - Document top-level photo and video resign in the handbook Postgres and in-memory listSignedMissingPhoto/Video skip replies, same as hashtags, so the function handbook states parent_id IS NULL. * 01a053a6 - Document fetchImpl and lnurlResponse in TSDoc debugRoutes takes a required fetchImpl for the mint probe. requestZapInvoice always returns lnurlResponse on every result path. * 01a053a6 - Document NIP57_PROBE in CONTRIBUTING and SPEC List the e2e-only mint-probe skip in the env table and Playwright pin list. POST /debug/accounts still probes unless NIP57_PROBE is 0. * 01a053a6 - Document NIP57_PROBE and meRoutes nostrKek The e2e mint-probe skip is an env var; list it next to DEBUG_TOKEN. meRoutes TSDoc now includes optional nostrKek for the probe. * 01a053a6 - Drop duplicate NIP57_PROBE CONTRIBUTING rows Keep one env-table row next to DEBUG_TOKEN and the Playwright pin next to the other blank pins. * 01a053a6 - Treat the custodial PN channel as in-v1 in CONCEPT Private messages on /conversations ship in v1; only client-side signing and Zap receipts stay deferred. In-api lists forum replies, public notes, and the NIP-57 mint probe. * 01a053a6 - List public note, replies, and conversation-id routes in CONTRIBUTING The DATABASE_URL purpose cell now names GET /messages/:id, replies, and GET/POST /conversations/:id alongside the existing message and conversation surfaces. * 01a053a6 - Do not resign notes that already have replies Photo, video, and hashtag missing lists skip top-level parents with children so resetSignedEvent cannot mint a new kind:1 id under live NIP-10 threads. * 01a053a6 - Refuse resetSignedEvent when a note already has replies In-memory and Postgres reset keep published event ids if any child row exists, closing the window between listSignedMissing* and the UPDATE. * 01a053a6 - Document child-skip on resign and newest-first published ids Port TSDoc and handbook match resetSignedEvent and missing-hashtag lists: top-level, no children. listPublishedEventIds is newest-first. * 01a053a6 - Refuse clearSignedEvent when a note already has replies Pending parents with children keep their event id so re-sign cannot orphan NIP-10 e-tags, matching resetSignedEvent. * 01a053a6 - Document child skips on clearSignedEvent and missing lists Handbook and in-memory listPublishedEventIds TSDoc match the no-children guards on clear, reset, and photo/video/hashtag resign lists. --------- Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
* 01a053a6 - Retarget platform threads and timestamp NIP-17 from the rumor
PATCH platform:true points every member_platform thread at the new
official account. Outbound wraps resolve the current platform pubkey.
Inbound kind:1059 uses rumor created_at, not the wrap clock.
* 01a053a6 - Cover member_platform outbound wrap to the live platform key
Worker recipient resolution for member→platform must hit the current
isPlatform account even when the thread still stores a stale accountB.
* 01a053a6 - Document platform-thread retarget on PATCH /debug/accounts/:id
The endpoint handbook now matches debugRoutes: platform:true rewrites
every member_platform row when the conversation store is wired.
* 01a053a6 - Publish the platform wrap on the second worker tick
Outbound conversation tests sign on tick one and publish on tick two.
The new member_platform cases now follow that same sequence.
* 01a053a6 - Mirror the passing outbound wrap fixture for member_platform
Use a real counterpart account with a Nostr key as accountB so the
worker can sign. The live isPlatform lookup and the no-key fallback
still run on that same fixture.
* 01a053a6 - Do not retarget a platform account's own member thread onto itself
Contact can open member_platform for an account that later becomes the
official platform. Bulk account_b rewrite then skipped that member row
so outbound wraps cannot target the sender pubkey.
* 01a053a6 - Mint debug sessions and e2e a real note plus reply
POST /debug/accounts/:id/session issues a member bearer. Playwright then
posts a note, reads it publicly, posts a reply, and asserts GET replies
returns { messages } with replyCount 1 — not a 401 stub.
* 01a053a6 - Match e2e:check path for POST /debug/accounts/:id/session
The gate looks for the literal handbook path, not a UUID interpolation.
* 01a053a6 - Cover debug session clock default and avoid self-wraps
Omit now in one session mint so Date.now is used. Live platform pubkey
is skipped when the sender is that account. CONTRIBUTING lists the new
session route and forum-replies e2e spec.
* 01a053a6 - Wait out post rate-limit in reply e2e; document debug session
Forum e2e slept 11s between note and reply so PostRateLimiter is not 429.
SPEC lists POST /debug/accounts/:id/session. issueSession Used-by includes
the debug mint.
---------
Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
* Serve forum videos as a sized faststart body iPhone MP4s store moov after mdat, and a streaming Response dropped Content-Length so Cloudflare ignored Range. Remux on write and GET, return a Uint8Array body, and add optional imeta dim/size on new notes. * Harden faststart remux and write the healed file atomically Abort remux when chunk-offset tables are truncated, compressed, or missing, or when the file has more than one mdat/moov. Heal-on-read writes a sibling temp file and renames it so a failed write cannot truncate the original. Range responses copy the slice. * Document faststart abort cases and unique heal temps Handbook now matches the remux abort list. Heal-on-read temp files use a UUID so concurrent GETs cannot share a PID-only name. * Cover faststart abort and heal-failure paths CI 100% coverage missed the 64-bit box header, WebM copy branch, heal write failures, a missing on-disk video at sign time, and an empty remuxed GET. Handbook lists truncated stco tables and UUID heal temps. * Cover tkhd-walk miss paths and qualify faststart docs isoBmffDisplaySize now hits a non-tkhd moov child and a truncated moov payload. Handbook and TSDoc no longer claim remux always succeeds. * Cover empty tkhd and non-container remux siblings Closes the last video.ts coverage holes: a tkhd with no payload and a remux that walks mvhd next to stco. * Spy-friendly fs namespace so heal unlink success is covered readForumVideoBytes uses import * as fs so tests can fail rename after a real temp write. That takes the inner unlink-success branch the 100% coverage gate still required. * Inject disk ops so heal rename-failure cleanup is testable node:fs/promises.rename cannot be spyOn'd. readForumVideoBytes takes an optional io so the rename-fail path unlinks a real temp and covers the remaining branch. * 01a057df - Drop the dead moov conjunct in the tkhd walk moov is already an ISO-BMFF container, so the extra type check could never be false. The sibling box walker already uses the set alone. * 01a057df - Rewrite size-0 moov headers and ignore unknown tkhd A last-box size of 0 would swallow mdat after a remux. Write the real 32-bit size first. Display size only reads ISO tkhd versions 0 and 1. * Prettier-format video.test.ts CI lint failed on formatting after the size-0 moov header rewrite. * Drop the unreachable 4 GiB size-0 remux abort Forum videos are capped at 32 MiB, so rewriting a last-box size of 0 always fits uint32. The overflow branch was uncovered in CI. --------- Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
* 01a053a6 - Drop NIP-17 wraps whose rumor has no created_at unwrapNip17 requires a finite rumor timestamp and the worker stores that unix time for kind:1059. Kind:4 still uses the envelope clock. * 01a053a6 - Give kind:4 ingest the envelope createdAt field The worker always reads rumor or envelope unix time from the same plain object. Kind:4 now returns createdAt from the wrap event so the union matches unwrapNip17. * 01a053a6 - Skip inbound DMs whose unix time is not finite plain.createdAt is optional on the kind:4/NIP-17 union. Fall back to the envelope clock, then skip the event if neither timestamp is a number. * 01a053a6 - Ignore the unreachable non-finite DM timestamp skip unwrapNip17 already requires a finite rumor created_at. Kind:4 copies the envelope clock. The TypeScript union still types the field optional. * 01a053a6 - Use the decrypted DM createdAt without a wrap-time fallback Both unwrapNip17 and kind:4 ingest now always set createdAt. The nullish coalesce to signed.created_at was an uncovered dead arm. * 01a053a6 - Type inbound DM plaintext as always having createdAt Give the decrypt callback an explicit return type so kind:4 and NIP-17 share one shape. Kind:4 still copies the envelope clock. * 01a053a6 - Narrow kind:4 envelope created_at before returning plaintext TypeScript does not treat Number.isFinite as a number guard on the optional event field. A typeof check assigns a definite unix time. --------- Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
* Require MEDIA_DIR at boot so videos never land in temp Missing or blank MEDIA_DIR now throws before listen. The image pins /data/media; tests and Playwright set the variable themselves. * Document MEDIA_DIR in Quick start Local bun run dev does not boot without the directory. Pin a temp dir in CONTRIBUTING and README so the documented entry point still works. * Read forum videos from process MEDIA_DIR in the worker The nostr tick env is a publish-flag slice and omitted MEDIA_DIR, so dim/size lookup threw and imeta dropped those tags after the boot guard. --------- Co-authored-by: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
TaprootFreak
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automatic Release PR
Commits: 1 new commit(s)