# Live deployment checks

Explicit endpoints, public findings, and cleanup evidence.

This project is separate from `test-environments` and `test-extended`. It has no
public service defaults. Missing configuration fails all checks with a clear
setup error; no check is silently skipped.

## Read-only use

Set both endpoints, then run:

```sh
npm run test-live-services
```

| Setting | Purpose |
| --- | --- |
| `NOSTRBASE_LIVE_RELAY` | Explicit `ws://` or `wss://` relay URL. |
| `NOSTRBASE_LIVE_BLOSSOM` | Explicit HTTP(S) Blossom origin, with no path. |
| `NOSTRBASE_LIVE_SEARCH` | Optional NIP-50 query; default `nostr`. |
| `NOSTRBASE_LIVE_BLOB_SHA256` | Optional existing public blob to download and verify. |
| `NOSTRBASE_LIVE_BLOB_FORMAT` | Write fixture: `text` (default), `json`, or `png`; MIME type matches the generated bytes. |

Endpoint credentials, query strings, and fragments are rejected. Redirects are
rejected. The report never contains keys, authorization headers, event bodies,
or downloaded content. It contains the configured endpoints, declared software
and versions, capability decisions, completion status, and counts.

Read-only mode uses a temporary identity for a signed Blossom list request.
It sends no relay publications, uploads, or deletions. It makes bounded relay
requests, an optional search, pull-only reconciliation, Blossom GET/HEAD/list
requests, and browser CORS OPTIONS preflight requests. No private key is needed.

## Explicit write tests

To also run scoped write checks, set all these values:

| Setting | Required value |
| --- | --- |
| `NOSTRBASE_LIVE_WRITE` | Exactly `1`. |
| `NOSTRBASE_LIVE_TEST_KEY` | Private key of a dedicated test account, as 64 lowercase hex characters. |
| `NOSTRBASE_LIVE_TEST_PUBKEY` | The matching public key, as 64 lowercase hex characters. |
| `NOSTRBASE_LIVE_NAMESPACE` | Dedicated namespace starting with `nostrbase-integration-`; remaining characters are letters, digits, `_`, or `-`. |

Supply the test key through your local environment or secret manager. The suite
checks the account match and namespace before contacting the deployment.
Read-only mode rejects write identity settings to prevent an ambiguous mode.

Six checks keep read completion, declared capabilities, Blossom routes, seeded
NIP-50 search, record writes, and blob writes separate. A failed relay check does
not prevent an independent Blossom write check.

The record write check creates one unique record, verifies acknowledgement and
readback, and requests signed deletion. Declared reconciliation also uses a fresh
SDK instance; successful query fallback can recover the record independently of
the strict declared-Negentropy check. The blob write check uses a tiny unique
fixture, verifies hash and byte identity, lists it, and requests deletion. PNG
mode generates a valid one-pixel image with a unique text chunk (158 bytes in the
public run). Declared NIP-50 support gets a separate signed kind-1 note with a
unique alphanumeric search token and bounded indexing retries; cleanup requests
NIP-09 deletion even when publication fails.
A relay acknowledgement or deletion request does not prove permanent storage
or removal of every remote copy.

## Local validation of the same project

```sh
node integration/relay/prepare.mjs
node integration/blossom/setup.mjs
NOSTRBASE_LIVE_LOCAL=1 npm run test-live-services
NOSTRBASE_LIVE_LOCAL=1 NOSTRBASE_LIVE_WRITE=1 npm run test-live-services
```

Local mode starts digest-pinned **strfry 1.1.3** and unmodified
**hzrd149/blossom-server 6.4.0**, commit
`32567afb15255c171817a78ed2861cd9e57bf4de`. It uses random loopback ports,
temporary storage, a generated test account, and a fresh test namespace. It
rejects external endpoints and external key settings. Docker and the cached,
verified Blossom checkout must be ready. The local run exercises exactly the
same compatibility tests as the external run.

## What each result proves

- NIP-01: the relay completes a bounded read and the SDK verifies returned
  signatures. Empty results are valid.
- NIP-50: a declared search extension must complete a real search request.
  Without a known seeded result, this proves protocol completion, not search
  ranking or indexing quality. If the relay does not declare NIP-50, the report
  states that the extension was not exercised.
- NIP-77: `supported_nips: [77]` or the legacy `negentropy: 1` advertisement
  counts as declared support. It must complete actual Negentropy negotiation with
  `strategy: negentropy`; silent query fallback fails. Read-only mode can have
  an empty matching dataset. Opt-in writes also test signed event transfer.
  An undeclared extension uses an explicit completed query and is reported as
  unexercised.
- Blossom: GET/HEAD routes, SDK error mapping, optional verified blob bytes,
  authenticated list behavior, and required browser preflight headers. A list
  route may be absent or deny the temporary identity; the report records that
  policy result. These HTTP header checks do not replace real browser tests in
  the separate Blossom service project.

Reports are saved under `output/environment/live/compatibility-*.json`.
The public driver keeps per-run Vitest results, compatibility reports, and logs
in a separate folder, so it does not replace the main environment suite result.

```sh
NOSTRBASE_LIVE_BLOB_FORMAT=png node integration/live/run-public.mjs wss://relay.damus.io https://blossom.band --write
node integration/live/probe-negentropy.mjs wss://relay.damus.io
```

The driver requires explicit endpoints. It generates a dedicated identity and
namespace, runs read-only checks, then runs writes only with `--write`. It never
prints the key. Before writes, it journals that generated key with mode `0600`
under `output/integration-cache/live-identities/`, outside the CI artifact paths.
It removes the key only when every attempted resource has a confirmed cleanup.
An absent report or unknown/failed cleanup retains the key for an operator retry.
Safe reports record namespace, record/event IDs, blob hashes, and acknowledgements.

The Negentropy probe uses the exported, unmodified Applesauce negotiation helper
and a real WebSocket. It sends an empty-vector pull request, never an event write,
and records bounded server notices and negotiation completion. It can distinguish
a metadata mismatch from an actual wire rejection.

## Public results, 2026-10-04

Public reads and writes were authorized for this run. Checks ran from roughly
09:00 to 09:17 UTC. Public compatibility failures remain failed assertions.

| Service | Observed result |
| --- | --- |
| `wss://relay.damus.io` | NIP-11 software strfry `1.1.0-158-gb705403ddf49`. Signed reads completed. A record publication, fresh-client query recovery, readback, and signed deletion passed in a complete record check. Some other runs had transient WebSocket/read failures. |
| Damus Negentropy | NIP-11 advertises legacy `negentropy: 1`, but omits NIP-77 from `supported_nips`. SDK falls back to a query. Direct modern negotiation receives `NOTICE: ERROR: bad msg: negentropy disabled`; the strict declared-capability check fails. |
| `wss://search.nos.today` | NIP-11 software `searchnos`, version `v0.1.0-841b8f6`. Actual NIP-50 search completed with verified signed results. General reads fail with `error: search filter is required`. Synthetic kind-1 publication and deletion both reject with `blocked: writes disabled`; seeded indexing remains unverified. |
| `https://blossom.band` | GET/HEAD absence mapping, signed list, and browser preflight passed. Tiny text was rejected with HTTP 400 and a MIME mismatch; JSON was rejected with HTTP 415 and an unsupported-file-type policy. A valid 158-byte PNG upload and signed deletion succeeded. |
| Blossom CDN bytes | The validated PNG descriptor redirects with HTTP 307 from an account subdomain of `blossom.band` to `image.nostr.build`. An unauthenticated, bounded redirect diagnostic retrieved exactly the uploaded 158 bytes and verified SHA-256. The SDK download check still fails because the documented SDK policy is `redirect: error`. No redirect policy was changed. |

Blossom did not declare an application version; `server: cloudflare` identifies
its HTTP front end. Descriptor diagnostics omit URL query strings and credentials
from their recorded redirect hops. Public tests used only synthetic records,
notes, and fixtures.

An initial Damus cleanup failed after an acknowledged synthetic record write.
The first driver kept its account key only in memory, so that cleanup cannot be
retried and one synthetic record may remain. Later confirmed deletions are
recorded individually. The current driver journals generated keys before writes
and retains them when cleanup is not confirmed; it makes no claim of global
record erasure.

Known scope of the possible remaining synthetic record:

```text
Relay: wss://relay.damus.io
Timestamp: 2026-10-04T09:01:40.825Z
Namespace: nostrbase-integration-d9d6db45-e7bb-4c68-91fb-2a20f10128c0
Author: 803e83bac266d35f106949450f2f2f8aecd6e9a53b5b598a5b99e8c1c9d35594
Row/event IDs: not retained
```

Evidence is saved in
`output/environment/live/compatibility-1791104500826-73172.json` and
`output/environment/live/1791104494399-ba41341b-7b6c-43b2-bfee-f6e04f26e76f/summary.json`.

Upstream local service logs remain in the usual relay and Blossom output folders.

The local services do not declare NIP-50. Its public request-completion branch
was verified against search.nos.today; seeded search was blocked by that relay's
write policy. Hosted routing, certificates, policies, and availability remain
specific to the selected deployment. These runs do not prove every public
service is compatible.
