Skip to content

Release: develop -> main - #89

Merged
TaprootFreak merged 5 commits into
mainfrom
develop
Sep 1, 2026
Merged

Release: develop -> main#89
TaprootFreak merged 5 commits into
mainfrom
develop

Conversation

@github-actions

Copy link
Copy Markdown

Automatic Release PR

Commits: 1 new commit(s)

  • Review all changes
  • Verify CI passes
  • Merge when ready for production

TaprootFreakAI and others added 5 commits August 31, 2026 22:58
… 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
TaprootFreak merged commit 0ff37bc into main Sep 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants