Skip to content

Nostr NIP-23: remaining work (v1 CLI shipped) #454

Description

@drawmeanelephant

Read this first (session handoff, 2026-08-16)

v1 is shipped on afterparty. Do not re-implement plan/sign/publish. Do not treat the numbered spec below as a todo list. This parent stays open so remaining work has a home.

First test (2026-08-16)

#591 merged. That is the first-tester live-smoke record (#583). Classification partial: nos.lol, nostr.mom, and relay.primal.net accepted event 0ef0b100…; relay.damus.io failed WebSocket upgrade (BadStatus) twice. Fixture: docs/contracts/fixtures/nostr-live-smoke/. No secret in git. Not a CI gate. Not a verified target.

Tester review: the testing client said they do not need more testing. Do not ask for another live smoke, another first-person try, or more first-tester evidence unless someone asks. This does not make Nostr a verified target and does not start #584.

Next session — start here

Card Do this? What it is
#583 Done. Closed by #591. Do not reopen. Recorded first-tester live-smoke fixture (partial).
#584 Only if someone authorizes verified-target extras. STATUS roadmap item 7. Not implied by the shipped CLI or by #591. Location adapter, Proof Pack, live-smoke gate. Damus BadStatus on this run is relay weather, not a reason to start #584.
Parked Do not start unless someone asks. NIP-42, NIP-09 unpublish, editor publication controls, comments, short notes, remote signers.

There is no default next Nostr product card. Next session starts only if someone wants #584 or a parked item.

Do not reopen: #492 #493 #494 #495 #496 #497 #545 #552 #566 #571 #583.

Do not redo: plan / sign / publish / DNS / wss:// handshake / NIP-19 npub/naddr / HTML nostr:naddr / the live-smoke fixture. Those are on afterparty via #468 #544 #551 #562 #568 #573 #591.

Authority (read before coding)

Start here Why
docs/contracts/nostr-publication.md Living contract. The spec below this line is historical.
content/guides/nostr-publication.md Operator guide
docs/contracts/fixtures/nostr-live-smoke/ First-tester publish report (partial)
docs/STATUS.md Nostr row + roadmap item 7 Product sentence: CLI shipped, not a verified target
First-person try (comment on this issue, 2026-08-16) How to re-run if someone asks. Tester said no more testing is needed.

Constraints for the next session

  • Integration line is afterparty (main is frozen). Fresh topic branch from up-to-date afterparty. Do not commit or push to main. Do not force-push.
  • Secret never on argv, in the profile, in the environment, or in git. publish never sees the secret. Ordinary CI never needs a real key or a public relay.
  • Nostr is a shipped CLI family, not a verified target. Do not invent a parallel pipeline or treat Pages / Standard.site as “not real.”
  • Previous Nostr worktrees: worktrees/grok-nostr-hostname (naddr HTML) and worktrees/grok-nostr-live-smoke (docs(nostr): record a first-tester live-smoke fixture #591). Start a new owned branch if more work is authorized.

The original specification from §1 down is historical. §17 acceptance is checked. NIPs pin 656cecc7.


Publication: add Nostr NIP-23 long-form target

Type: Product/protocol architecture
Status: v1 CLI shipped on afterparty (2026-08-16). Remaining work is verified-target extras and §20 future items, not the original three slices.
Target: Nostr long-form articles (NIP-23 kind 30023)
Research date: 2026-08-14
Living contract: docs/contracts/nostr-publication.md
NIPs authority: nostr-protocol/nips at 656cecc7c0a815b6a2b218d3b5d6f078b3f4dbab, committed 2026-08-08

The original specification below is historical. v1 plan/sign/publish, NIP-19 naddr/npub, HTML nostr:naddr, and the first-tester live-smoke fixture (#591 / #583) are on afterparty. This issue stays open as the parent. Remaining card: #584 (verified-target extras, only if authorized). §20 stays parked.

1. Opportunity

Boris should be able to syndicate selected Boris-authored articles to Nostr without weakening the ordinary static-site pipeline:

Boris-authored publication
        ↓
deterministic unsigned Nostr publication plan
        ↓
explicit signing
        ↓
explicit relay publication

The canonical Boris website remains canonical. Nostr is another opt-in publication surface, alongside—not instead of—the website, RSS, sitemap, search, IR, RAG, Context Bundle, and llms.txt.

Boris must not become a full Nostr client, relay, social feed, wallet, or messaging application.

2. Current protocol research

Protocol authority for this issue is the official NIPs repository at the exact revision above:

  • NIP-01: event serialization, BIP-340 signing, addressable-event replacement, client/relay messages, and OK results.
  • NIP-23: long-form kind 30023, Markdown profile, metadata, update time, and references.
  • NIP-19: naddr encoding and human-facing key/address forms.
  • NIP-21 and NIP-27: nostr: URI references in content.
  • NIP-24: generic r web-reference tags and lowercase t topics.
  • NIP-09: deletion requests and their non-erasure limitations.
  • NIP-11, NIP-42, and NIP-65: relay information, relay authentication, and user relay lists.
  • NIP-07, NIP-46, NIP-49, and NIP-55: browser, remote, encrypted-key, and Android signer boundaries.
  • NIP-73: external-content identifiers; relevant to, but not automatically selected for, canonical web identity.

Material protocol facts at that revision:

  • A NIP-01 event ID is SHA-256 over the exact compact JSON serialization [0, pubkey, created_at, kind, tags, content]. It cannot be computed until the public key and created_at are fixed. The signature is BIP-340 Schnorr over secp256k1.
  • Kind 30023 is addressable. Its address is (kind, author pubkey, d); relays replace versions according to NIP-01's addressable-event rules. Relays may retain different versions and resolve equal timestamps by event ID.
  • NIP-23 says long-form content should be Markdown. Creating clients must not hard-line-break paragraphs and must not support adding HTML to the Markdown.
  • NIP-23 defines created_at as the last-update time and defines optional title, image, summary, published_at, and topic t tags. Editable articles should carry d.
  • Relay publication is ["EVENT", event]; the meaningful response is a matching ["OK", event_id, accepted, message]. Duplicate acceptance is still success. Rejection messages can carry machine-readable prefixes such as auth-required:, restricted:, rate-limited:, and invalid:.
  • NIP-09 deletion is a signed deletion request, not an erasure guarantee.
  • NIP-42 authentication is a separate, ephemeral signed event flow and may be required by individual relays.

The implementation must re-check the official NIPs head before landing. Protocol drift after the pinned revision must be recorded in the contract/PR.

3. Architectural fit

Nostr is an independent content projection followed by explicit mutation stages. It is not a static hosting adapter and should not be forced into the current verified-target seam described by docs/contracts/publication-platforms.md.

The conductor rule remains:

validated Boris model + source body
 ├── canonical website
 ├── RSS / sitemap / search / IR / RAG / Context / llms.txt
 └── Nostr event intentions
       └── signer
             └── relay delivery

A bare Boris build must retain current behavior and must never need a key, contact a relay, publish, delete, or otherwise mutate remote state. A failed Nostr plan/sign/publish operation must not invalidate, corrupt, or roll back a successful web build.

Publication facts belong in the strict publication profile. Document facts stay in the closed frontmatter contract. Do not add Nostr-specific frontmatter merely for convenience.

Coordinate with #452 because both proposals may touch publication-profile, CLI, and observed-network evidence contracts. Do not use that overlap to create a giant generic “federated publishing framework.”

4. Eligibility and selection

Required by protocol

  • Event content conforms to the NIP-23 Markdown profile.
  • An editable kind-30023 article has a stable d.
  • Topics emitted as t conform to NIP-24's lowercase convention.
  • Nostr references in content use the NIP-21/NIP-27 nostr: form.

Recommended and required by Boris v1

A page selected for full-content publication must:

  1. be selected by an explicit, exact entity-ID allowlist in a Nostr publication-profile section;
  2. come from the Markdown source family (.md or currently accepted .mdx source in Markdown mode);
  3. not have draft status;
  4. have an explicit frontmatter id rather than only a path-derived ID;
  5. have explicit title, summary, and published_at;
  6. pass the publication-safe Markdown validation below; and
  7. have a canonical public web URL derived from the same normalized site.url/publication-location machinery used by current URL-bearing projections.

Textile and Cooklang entities are not eligible for NIP-23 full-content publication in v1, even though Boris has bounded internal adapters for their HTML rendering. They must produce an actionable eligibility diagnostic, never silent conversion.

Requiring an explicit entity ID is a Boris stability rule, not a NIP requirement. It makes ordinary source-path renames preserve the Nostr article address without adding a side database.

Optional/future

Cross-dialect publication may be revisited only if Oliver gains a reviewed canonical Markdown serialization that preserves semantics and satisfies NIP-23. That is future work, not an implicit promise of the current Textile/Cooklang adapters.

5. Publication-safe Markdown

The smallest honest v1 mapping is neither raw source bytes nor rendered HTML:

  1. remove/skip Boris frontmatter using the existing parsed body boundary;
  2. start from the original Markdown body;
  3. apply only the existing deterministic Boris-owned source transformations needed to preserve the page's published meaning:
    • expand validated includes;
    • rewrite doc/wiki links to ordinary canonical Boris web URLs;
    • rewrite content-local image references to absolute canonical asset URLs;
  4. validate the resulting source structurally through the Oliver parsing seam; and
  5. emit those transformed Markdown bytes without a general normalize/reserialize pass.

This is a bounded “publication-safe Markdown view,” not HTML-to-Markdown and not a new Markdown engine.

The plan must fail closed when the selected page contains:

  • any raw HTML node;
  • Boris-only <Aside> or <Details> components, or any unconsumed Boris directive;
  • a hard-wrapped paragraph that would violate NIP-23's no-hard-line-break requirement;
  • an unresolved relative URL or content-local asset;
  • an unsupported source feature that cannot be represented as interoperable Markdown without semantic loss.

Validation must be syntax-aware so code spans and fenced code containing HTML-like text are not false positives. Do not silently strip content, emit rendered HTML, or claim compliance when validation cannot establish it.

Suggested diagnostic:

ENOSTRMARKDOWN <source>:<line>: selected NIP-23 article contains raw HTML;
remove/replace it with interoperable Markdown or opt this entity out of Nostr publication

Future canonical Markdown serialization from Oliver may remove the conservative hard-wrap restriction. It is not part of v1.

6. NIP-23 mapping

Use this fixed mapping and deterministic tag-group order:

Event field/tag Boris v1 source Classification
kind constant 30023 Protocol required for NIP-23
content publication-safe Markdown view Protocol profile required; Boris validation required
d exact explicit canonical Boris entity ID Addressability recommended by protocol; required by Boris
title explicit frontmatter title Protocol optional; required by Boris
summary explicit frontmatter summary Protocol optional; required by Boris
published_at frontmatter RFC3339 UTC converted to decimal Unix seconds string Protocol optional; required by Boris as first-publication provenance
t validated lowercase frontmatter tags, preserving source order after duplicate rejection Protocol topic convention; optional per article
image omitted in v1 Protocol optional; Boris has no owned document-image fact
r canonical Boris page URL NIP-24 interoperable web reference; Boris recommendation
created_at supplied at signing, recorded as last-update/signing time NIP-01 event field; NIP-23 update convention
authored Nostr refs preserved/validated NIP-21 nostr: URIs Protocol convention
Boris relations no automatic a, e, or q tags Deliberate non-mapping

Tag order is d, title, summary, published_at, zero or more t tags in validated source order, then r. The plan contract must define JSON string escaping and byte ordering exactly.

Do not infer image from the first Markdown image. Adding a document-level image later requires an explicit publication-model/frontmatter decision.

The NIP-24 r tag says the event refers to the canonical page; it does not establish protocol-wide canonical precedence. Whether v1 should additionally use NIP-73 ["i", <normalized-url>] plus ["k", "web"] remains an open review question. Do not invent a custom canonical tag.

7. Stable identity and updates

The Nostr article address is:

30023 : configured author pubkey : explicit Boris entity ID

Use the exact UTF-8 entity ID as d; do not hash it or derive it anew from the pathname. Boris already bounds entity IDs to 255 UTF-8 bytes, which also fits NIP-19's one-byte TLV length for naddr's identifier value. Validate collisions through the existing graph rules.

Consequences:

  • ordinary content edits keep the same address;
  • metadata-only edits keep the same address and produce a new version;
  • a source-path rename with the same explicit id keeps the same address, though a changed canonical URL is a metadata update;
  • changing explicit id or author pubkey creates a new address and must be reported as an identity migration;
  • path-derived IDs are rejected for selected v1 articles rather than accidentally creating new articles after rename.

No side database is required.

A newer signed event for the same address is an update. Boris cannot promise every relay sees or retains the same version.

8. Deterministic unsigned publication plan

Define a versioned artifact such as boris-nostr-publication-plan. It is an event-intention document, not a syntactically incomplete NIP-01 event.

The root records:

  • format/schema version;
  • Boris compiler/version;
  • pinned NIPs repository/commit/research date;
  • normalized public site identity;
  • expected author public key (lowercase 64-hex, public—not secret);
  • normalized ordered relay targets;
  • deterministic source/config provenance;
  • ordered article intentions.

Each article records at least:

  • entity ID and source path;
  • canonical web URL;
  • kind 30023;
  • stable d;
  • exact ordered tags;
  • exact event content;
  • SHA-256 content digest;
  • first-publication timestamp source and value;
  • created_at policy placeholder (signing-time in v1);
  • an event-intention digest that excludes relay destinations and signing time;
  • a delivery-config digest for relay targets;
  • eligibility/disposition where knowable.

Identical eligible source and configuration must produce byte-identical plan output. Planning performs no network requests, reads no private key, uses no wall-clock “now,” and does not claim an event ID or signature.

A source Git commit may be captured in a separate provenance wrapper or supplied explicitly; do not silently read a changing Git worktree and place it inside the byte-deterministic core plan. This preserves the current Boris publication-plan rule against ambient Git/time inputs.

9. Time semantics

Keep three concepts separate:

  • First publication time: source-owned published_at, deterministic, mapped to the NIP-23 tag.
  • Article-update time: NIP-23's created_at, supplied at signing.
  • Relay-observation time: when Boris observed an OK, rejection, or timeout; publish evidence only.

The offline plan must contain no wall-clock time. The signer uses current Unix seconds by default or an explicit test/recovery override, validates created_at >= published_at, and records the chosen value in the signed-event bundle. Because created_at participates in the event ID, signing is a controlled mutation of the plan intention, not a deterministic build phase.

10. Signing boundary and security

Define a narrow signer interface conceptually equivalent to:

get_public_key() -> 32-byte x-only pubkey
sign_event({kind, created_at, tags, content}) -> signed NIP-01 event

The sign stage must:

  1. verify that the signer pubkey matches the plan's expected author pubkey;
  2. serialize the NIP-01 preimage exactly;
  3. recompute the event ID;
  4. obtain a BIP-340 Schnorr signature;
  5. verify the signature before writing a signed-event bundle; and
  6. bind that bundle to the exact plan/event-intention digest.

Recommended v1 signer: one explicit local signer that reads a hex secret or nsec once from standard input or a dedicated inherited file descriptor/secret-manager pipe. Do not accept the secret in a command-line argument, publication profile, checked-in file, public environment dump, or retained evidence. Environment-secret support is not the default because process environments are frequently copied into diagnostics.

NIP-46 remote signers and NIP-07 browser signers are future adapters behind the same boundary. NIP-49 describes an encrypted key representation but does not authorize Boris to become a key store. Hardware/remote signers are future work.

The key must never enter:

  • site output;
  • the unsigned plan;
  • normal or error logs;
  • proof/evidence artifacts;
  • crash diagnostics where Boris controls formatting;
  • shell history;
  • Git history.

Secret buffers should be bounded and zeroed best-effort after use.

Dependency decision required before implementation: current Boris/Zig dependencies do not expose a reviewed BIP-340 signing path. A separate security review must choose between a narrowly pinned/audited native libsecp256k1 boundary and a reviewed pure-Zig implementation. Do not improvise signature code inside the feature.

11. Relay configuration and publication

The publication profile may contain public Nostr configuration only:

  • enabled;
  • expected author pubkey;
  • exact entity-ID allowlist;
  • explicit non-empty relay list;
  • publication-safe timeouts/retry limits.

There is no hard-coded public relay list and no implicit network discovery. Production relays must use wss://. ws:// is permitted only for explicit loopback/mock-test configuration.

Validate URL syntax, host, optional port/path, and reject userinfo, query, fragment, credentials, invalid schemes, and duplicate normalized relay URLs. Normalize scheme/host/default port/root path and order normalized relays bytewise in the plan/evidence.

Publishing sends the exact already-signed event and waits a bounded time for a matching NIP-01 OK. A NOTICE is not success. A malformed or wrong-event reply is an error. Preserve the relay's acceptance boolean and bounded message without treating arbitrary relay text as trusted markup.

NIP-11 relay information is optional publish-time observation; it must not make planning network-dependent or be treated as authoritative acceptance. NIP-65 discovery is future convenience, not v1 configuration truth.

If a relay requires NIP-42 authentication, v1 should fail that relay with an actionable auth-required/unsupported outcome unless the review explicitly promotes NIP-42 into the first slice. Authentication would require another ephemeral signing flow and must not be smuggled into ordinary event publication.

12. Relay result semantics

The publish report must record per relay:

  • normalized target;
  • connection result;
  • exact event ID sent;
  • whether a matching OK arrived;
  • accepted/rejected boolean;
  • normalized machine-prefix classification and bounded relay message;
  • timeout/unavailable/malformed-reply state;
  • observation timestamp and duration.

Overall classification must preserve distributed truth:

  • complete: every selected relay accepted the event, including accepted duplicates;
  • partial: at least one accepted and at least one rejected, timed out, or was unavailable;
  • failed: none accepted and all relays returned explicit rejection;
  • incomplete: none accepted and at least one outcome is unknown because of timeout/unavailability.

Do not collapse these outcomes into one “Published” boolean.

13. Idempotence, retries, and removal

Compare the current event-intention digest with an explicitly supplied prior signed/publish-evidence bundle:

  • no prior evidence: disposition is unknown/needs-signature, not a claim of “first publish”;
  • unchanged content and metadata: reuse the exact signed event; do not re-sign merely to change created_at;
  • changed content or NIP-23 metadata: create a new signed event with the same address and a newer created_at;
  • relay-list-only change: reuse the exact signed event and deliver it to the requested relays;
  • failed/timed-out relay retry: resend the exact signed event, preserving event ID and signature;
  • identity change: report an explicit new-address migration.

Local evidence can describe what Boris attempted and observed. It is not a remote consistency database and cannot prove that all relays retain the same latest version.

Omitting or deleting a local Boris file must do nothing remotely. V1 does not automatically emit a NIP-09 deletion request. A future, separately confirmed unpublish command may plan and sign kind 5 with the same author, an a coordinate and corresponding k tag, but must say clearly that relays and downstream clients may retain the article. Deleting a deletion request does not restore or erase history.

14. Evidence

Keep deterministic intent evidence separate from temporal signing/network evidence.

Plan evidence

  • Boris/compiler version;
  • source/config provenance supplied deterministically;
  • entity ID and source path;
  • kind and stable d;
  • canonical URL;
  • exact content/tag digests and ordered tags;
  • first-publication timestamp source;
  • relay targets;
  • protocol repository, commit, and research date;
  • eligibility/disposition.

Signed-event evidence

  • unsigned-plan digest;
  • signer public key;
  • actual created_at;
  • NIP-01 serialization/event ID;
  • signature verification result;
  • signed-event digest.

Publish observation evidence

  • signed-event digest/event ID;
  • relay-by-relay responses and bounded messages;
  • accepted/rejected/timed-out/malformed classifications;
  • overall distributed classification;
  • observation timestamps and durations.

These artifacts may adopt Boris's existing “configured / attempted / emitted / checked / passed / verified / failed / limited” vocabulary, but they must not pretend Nostr relays are target-local static hosts. The private key is never evidence.

15. Failure behavior

Provide stable, actionable diagnostics at the correct phase:

  • ENOSTRELIGIBILITY: non-Markdown source, draft, missing explicit identity, missing required title/summary/published_at, unsupported selected entity.
  • ENOSTRMARKDOWN: raw HTML, hard-wrapped paragraph, remaining Boris-only construct, unsafe/unresolved URL or asset, unsupported Markdown feature.
  • ENOSTRTIME: invalid timestamp conversion, out-of-range value, or signing created_at preceding published_at.
  • ENOSTRRELAY: invalid/duplicate relay URL, connection failure, timeout, malformed reply, rejection, partial publication, or unsupported authentication requirement.
  • ENOSTRSIGN: missing/invalid key input, public-key mismatch, serialization/hash failure, signing failure, or signature-verification failure.
  • ENOSTRPLAN: invalid schema, digest mismatch, or plan/signed-bundle mismatch.

Diagnostics identify the entity/relay, cause, and remediation without printing secret input or the complete process environment. Nostr failure never retroactively invalidates a successfully committed website.

16. CLI shape

Preserve the current truth that boris plan --profile PATH is a deterministic configuration declaration and does not scan/build content. Do not silently redefine it.

The contract review should settle one of these explicit shapes:

boris plan nostr --profile PATH --out nostr-plan.json
boris sign nostr --plan nostr-plan.json --key-stdin --out nostr-events.json
boris publish nostr --events nostr-events.json --report nostr-publish.json

or a bounded boris nostr plan|sign|publish command family.

Whichever spelling wins:

  • plan is offline and secret-free;
  • sign is explicit and never publishes;
  • publish is the only relay-mutating step;
  • deletion/unpublish, if ever added, is a separate confirmed command;
  • machine-readable output, quiet behavior, usage errors, and exit classes follow current Boris CLI conventions;
  • per-relay partial success is represented in the report and exit contract.

17. Acceptance criteria

Shipped in v1 (slices #468 / #544 / #551 / #562 / #568 / #573). Boxes checked 2026-08-16.

  • Boris can produce a versioned NIP-23 publication plan offline without private keys, relay access, DNS, wall-clock time, or ambient Git data.
  • Identical eligible source and configuration produce byte-identical unsigned plan bytes.
  • A selected article requires an explicit Boris entity ID and retains the same (30023, pubkey, d) address across ordinary content edits and source-path renames that preserve that ID.
  • Kind, content, d, title, summary, published_at, topics, canonical web reference, and deterministic tag ordering follow the pinned protocol mapping above.
  • Frontmatter never appears in event content.
  • Markdown containing raw HTML, hard-wrapped prose, unresolved Boris-only constructs, or unsafe relative resources is rejected with an actionable diagnostic rather than silently transformed.
  • Textile and Cooklang are not silently converted to Markdown.
  • No rendered HTML or HTML-to-Markdown conversion enters the NIP-23 path.
  • Planning does not claim an event ID/signature; signing supplies created_at, creates the exact NIP-01 ID, produces BIP-340 signature bytes, and verifies them.
  • The signer public key must match the planned public identity.
  • Private key material never enters site output, profiles, plans, logs, retained proof/evidence, or Git history.
  • Relay destinations are explicit, normalized wss:// URLs with no protocol-truth default list.
  • Relay publication reports accepted, rejected, timed-out, unavailable, and malformed outcomes per relay and preserves partial success honestly.
  • Retrying an unchanged event resends the exact signed event rather than creating a needless update.
  • Removing a local article performs no automatic deletion/unpublish action.
  • A failed Nostr operation never corrupts or invalidates the canonical Boris website build.
  • Ordinary CI requires neither a real secret key nor public relay access.
  • Normative contracts, focused fixtures, diagnostics, CLI docs, and a uniquely named changelog fragment ship with implementation.

18. Test plan

Unit

  • NIP-23 field mapping and fixed tag-group ordering;
  • explicit-ID stability, collision rejection, rename behavior, and 255-byte boundary;
  • Markdown/Textile/Cooklang eligibility;
  • AST-aware raw-HTML/component detection;
  • hard-wrapped paragraph rejection without code-fence false positives;
  • deterministic include/link/wiki/asset projection;
  • deterministic unsigned plan bytes and digests;
  • RFC3339-to-Unix conversion and signing-time validation;
  • Unicode in title, summary, body, entity ID, and links;
  • canonical URL mapping under root/subpath publication;
  • relay URL normalization/deduplication;
  • prior-evidence disposition and retry behavior.

Fixtures

  • simple Markdown article;
  • topics;
  • summary and confirmation that image is omitted in v1;
  • long Unicode Markdown;
  • authored NIP-21 reference;
  • raw HTML;
  • <Aside>/<Details>;
  • hard-wrapped paragraph;
  • Textile rejection;
  • Cooklang rejection;
  • source rename with stable explicit ID;
  • unchanged article;
  • changed body;
  • metadata-only change;
  • content-local asset and rewritten canonical link.

Signing

Use deterministic, clearly test-only keys/fixtures. Verify exact NIP-01 serialization, SHA-256 event ID, BIP-340 signature, public-key match, and signature verification against authoritative upstream vectors where possible. Test malformed key, wrong signer, changed-plan digest, and bad signature. Never use a production key.

Relay

Use a local mock WebSocket relay for acceptance, accepted duplicate, rejection/prefix classification, timeout, unavailable connection, malformed/wrong-ID reply, NIP-42-required response, and mixed multi-relay results. Verify that retries preserve the exact event ID.

An optional bounded manual test may publish a disposable test-identity article to explicitly selected real relays. It is never required by ordinary CI and must not use a personal/production identity.

19. Non-goals

  • implementing a Nostr relay;
  • implementing a full Nostr client;
  • timelines or feed reading;
  • DMs;
  • wallets or zaps;
  • reactions;
  • comments/replies in v1;
  • account/profile management;
  • Textile-to-Markdown conversion;
  • Cooklang-to-Markdown conversion;
  • HTML-to-Markdown conversion;
  • replacing RSS;
  • replacing the canonical Boris website;
  • automatic private-key storage;
  • automatic deletion from relays;
  • automatic relay discovery in v1;
  • a giant generic federated-publishing framework.

Do NIP-23 well first.

20. Future work

  • cross-dialect publication if Oliver gains canonical Markdown serialization;
  • Nostr comments/replies;
  • short-note syndication;
  • Nostr identity/nostr:naddr links from generated pages; shipped (feat(nostr): emit NIP-19 naddr and accept npub (#566) #568 plan naddr, feat(nostr): emit nostr:naddr alternate links from the HTML build (#571) #573 HTML alternate links);
  • editor publication controls;
  • NIP-07 browser and NIP-46 external signer integration;
  • hardware/remote signer adapters;
  • relay discovery conveniences using NIP-65/NIP-11;
  • NIP-42 authenticated-relay support if not promoted into v1;
  • publication-status UI;
  • document-image metadata after a publication-model decision;
  • explicit, reviewed NIP-09 unpublish planning;
  • Nostr references derived only from explicitly mapped Boris relations.

21. Dependencies and review gates

Before implementation:

  1. re-check the official NIPs head and record any drift from 656cecc7...;
  2. add/review normative contracts for Nostr profile configuration, publication-safe Markdown, unsigned plan, signed bundle, relay report, diagnostics, and CLI behavior;
  3. decide the BIP-340 implementation dependency through a security-focused review;
  4. decide whether the canonical URL uses only NIP-24 r in v1 or also NIP-73 i/k;
  5. decide whether authenticated relays are explicitly unsupported in v1 or whether the first slice includes NIP-42;
  6. decide the exact CLI namespace without changing legacy plan output;
  7. retain enough parser provenance to distinguish explicit from path-derived entity IDs;
  8. coordinate publication-profile/CLI/evidence vocabulary with Publication: add a Standard.site / AT Protocol target #452 while keeping the Nostr projection independent.

22. Recommended first slice

The first implementable slice should stop at an offline, byte-deterministic boris-nostr-publication-plan for an explicit allowlist of Markdown articles with explicit IDs, title, summary, and published_at. It should include publication-safe Markdown validation, canonical URL rewriting, fixed NIP-23 mapping, relay configuration validation, and fixtures—but no private-key ingestion, cryptography, WebSocket client, relay authentication, or remote mutation.

A second reviewed slice can add signing after the crypto dependency decision. A third can add explicit relay publication and per-relay evidence. This sequencing proves the content/identity contract before the security- and network-sensitive layers arrive.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions