Back to Body Journal
SOURCE / PINNED RELEASE

Made of little things.

Body Journal

Release
e8049f8da4e2…
Author-recorded commit
61c04bf0e8aa…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6c4554844c7

Archive hash verified: 4369d778da046a8b…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

docs/ai-connections.md
# AI connections and saved sessions

> Legacy bridge reference. The active UI uses [shell LLM or paid CVM](llm-value-reference.md); the provider-key editor and raw diagnostics described below are no longer active.

## Build brief

Purpose: let an adult review their symptom record, choose their own inference
provider and request a preliminary report through a self-hosted service.

- Use the updated Body Journal palette: white surfaces, off-black text,
  reddish orange emphasis, and readable forms. Connections use a scrollable
  dialog with explicit saved / untested / testing / verified / failed states.
- NAPs: optional `storage` for connections, remembered keys and autosaved cases;
  optional `cvm` for the AI bridge; existing optional `config` for sizing.
  Core mapping still works with all of these absent. No hard requirements.
- SDK helpers: `storage.getItem/setItem/removeItem`, `cvm.callTool/close`.
  No relay escape hatches, new NAP domains, direct browser networking, or shell
  implementation. The separately run service owns vendor HTTP requests.
- Connections contain a provider, editable model ID, and label. Remember API key
  opts keys into the saved connection record; unchecked keys stay in memory.
  Keys are separated by connection ID/provider and excluded from case records.
- A user-specified ContextVM server receives keys, selected case text, reviewed body-map images and opted-in patient photos. It
  must be operated by the user or someone they trust. The provided bridge
  requires encrypted transport and does not log or persist request bodies.
- Verified soyLI 0.31.3 host transport uses `EncryptionMode.REQUIRED` (installed
  binary inspection, 2026-10-24). Generic CVM availability alone does not establish
  this policy on other hosts; deploy only with a host enforcing encryption.
- Connection tests use synthetic text and a random character image and can incur vendor charges.
  Case requests require a separate review/send gesture. No automatic requests,
  retries, fallback vendors, or diagnosis triggered by a form change.
- This increment sends structured text and reviewed body-map illustrations, including
  plain-language locations and recorded poses. Rendered anatomy and painted marks
  are explicitly annotations, not photographs, examination findings or lesions.
  Patient photos are separately labeled and require explicit inclusion. PDF and
  other document contents remain excluded; see [photos-and-reports.md](photos-and-reports.md).
- Sessions restore on startup and save after edits through host storage, without
  an encryption guarantee. The existing draft slot is upgraded in place. The
  completed report is included; credentials and pending requests are excluded.
  Read failures block autosaving over the stored record until an explicit retry.
- Tiny frames keep the useful resize notice; phone connection/review dialogs
  scroll; desktop retains the existing three-column body workspace. App-owned
  palette must survive host dark/light and missing optional domains.

## Sources checked 2026-10-24

- [OpenAI Chat Completions](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)
  and [GPT-5.4](https://developers.openai.com/api/docs/models/gpt-5.4).
- [Kimi quickstart](https://platform.kimi.ai/docs/get-your-api-key):
  `https://api.moonshot.ai/v1`, `kimi-k3.1-pro`.
- [DeepSeek models](https://api-docs.deepseek.com/quick_start/pricing/):
  `https://api.deepseek.com`, `deepseek-flash`.
- [ContextVM encryption](https://contextvm.github.io/contextvm-docs/how-to/encryption/).

These are integration presets, not a clinical-quality ranking. Model IDs can
change and are editable; a successful synthetic test establishes basic access,
not medical accuracy. A successful visual challenge also establishes image reading for that tested connection. The service accepts only the
three known vendor origins, with redirects rejected.

## Running the bridge

Use Node 24 or later. Run `pnpm ai:bridge` from this project after installing its
locked dependencies. The service is a separate process, never bundled into the
napplet. It generates a temporary server identity and prints only its public key
and configured relays. Paste those values into AI connection → Your AI bridge.
The default relay is `wss://relay.contextvm.org`; no public server announcement or
relay-list profile is published. Tool traffic is encrypted, but the relay can
observe encrypted traffic and may retain its envelopes. The bridge and selected
vendor necessarily receive the submitted plaintext.

For a stable service identity, set `BODY_JOURNAL_SERVER_KEY` to a separate random
32-byte hex key in the process environment. Keep it outside the project and do
not reuse a personal Nostr signing key. `BODY_JOURNAL_RELAYS` is a comma-separated
list of up to three WSS URLs. Loopback WS is supported for transport tests; the
host may separately restrict relay destinations. Optionally set
`BODY_JOURNAL_ALLOWED_CLIENTS` to comma-separated 64-character transport public
keys. These are host transport identities, not the viewer's social profile.

There are no server-side inference credentials. Every request brings its own
provider key. The service supports only OpenAI, Kimi and DeepSeek origins; it does
not accept caller-provided HTTP URLs or follow redirects. No request bodies,
keys, responses, or reasoning traces are logged or saved by our service code.
Stop with Ctrl+C. A restart changes the public key unless a stable key is set.

Connection setup and sending are distinct. **Save connection** stores a label,
provider, model, active connection ID, bridge public key and relays through the
host. **Test connection** first checks the bridge contract without credentials,
then makes a text request and a random-image reading request (up to two billed calls). Only an exact image reading verifies vision; an explicit image-support rejection is distinguished from an inconclusive result. Verification lasts 30 minutes and is not restored. The bridge's self-description is
not an encryption attestation: the host transport must require encryption. This
was verified for installed soyLI 0.31.3, not every host advertising CVM.

Saving reports progress and its result inside the connection dialog. Remembered
keys and trust for the exact bridge destination are included with the metadata.
Unchecking Remember API key or clearing the key removes its stored copy on the
next successful Save connection. A provider change clears that connection's key;
editing the bridge resets trust. Connection verification is never restored.
The button
shows **Saving…**, then **Saved** after the host confirms the write. A missing
storage domain explicitly reports **Saved for session**. Validation and storage
errors stay in the dialog and allow retry. Changes made during an outstanding
write require another save; saving preserves the current connection test result.

**Review AI request** prepares and shows body-map image sheets, coverage notes, the case and instructions. A successful visual challenge permits image sending; otherwise the user must test or explicitly choose text only. Sending makes one
request to that provider with no automatic fallback or vendor retry. The
connection-test and report-generation token caps differ; a successful test does
not guarantee a longer report will finish. Each service call requests a 12-second
budget (the installed host caps calls at 25 seconds). Inference runs as an in-memory
job with a 90-second server deadline; the client checks its status every 2.5 seconds
for up to 110 seconds. Completed results are readable for two minutes (memory cleanup runs every 30 seconds) and accessible
only to their originating transport actor. Repeated starts with the same request
ID and arguments reuse the job during that period. **Stop waiting** stops polling
and discards a later response;
NAP-CVM does not expose request-specific cancellation, so the bridge/vendor may
finish and charge for work already started. Starting a new request is explicit.

Protocol 4 stages images in 24,000-character encrypted MCP chunks and preserves their patient-photo or body-map category. Staging is transport-actor scoped, SHA-256 checked, immutable per ID, and held only in memory for five minutes from the first chunk. Images are consumed by analysis and discarded on completion or expiry; cancellation requests cleanup where the host remains available. Per-image transport size is 500,000 data-URL characters; saved patient photos have a tighter 65,000-character limit. Each actor may stage 50 images / 8,000,000 characters, with a four-actor-size global memory budget. No image is published or assigned a public URL. Restart older bridge processes after upgrading.

The bridge allows four concurrent vendor calls, one per transport actor, and ten
per actor per minute. Case JSON is limited to 24 KB; report JSON to 40 KB; upstream
non-streaming response bytes to 160 KB (SSE wire bytes to 2 MB). Unsupported, refused, incomplete or oversized results
are rejected. Only validated final structured content is accepted as a report;
unvalidated output appears separately as escaped diagnostic text.
Analysis uses bounded SSE streaming, with actor-scoped diagnostics retrieved in 2,000-character pages through the existing job result tool. Raw assistant output and provider-exposed reasoning stay in memory; no headers, request bodies or diagnostics enter logs or saved case records. Credentials are redacted. GET /models discovery uses the same trusted bridge without inference or case data. See [ai-diagnostics.md](ai-diagnostics.md). The request uses bounded output tokens; it does not request web search or tools.
OpenAI requests set `store: false`. This does not override any vendor retention
policy or assert equivalent retention controls for the other providers.

## Session behavior

The header and About you panel open **Your current session**. Forms, painted
anchors, poses, current step/selection, completed report, optimized photos and
fictional-example provenance save automatically under the existing `body-journal-case-v1` slot. Earlier
manual drafts restore on startup and upgrade on the next save. The session is
limited to 460 KB, with the case portion still limited to 380 KB. Keys are kept
in a separate, bounded connection record. Every restore validates data before use.
Relative onset answers retain the date they were reported; display colors are
rebuilt from trusted application values. Incomplete age input is preserved, but
adult eligibility is still checked before any AI submission.

Wait for **Session saved** before closing. Read failures leave stored data intact
and block autosave until an explicit retry or replacement. Write failures retain
the current work and offer **Save now**. **Clear current session** removes health
data and the report from memory and storage after pending writes settle, while
keeping AI connections. Reload never resumes a request or runs a connection test.
Storage scope and other limits are described in [persistence.md](persistence.md).

## Module map

- `src/ai-contract.ts`: provider presets, bounded report contract and instructions.
- `src/ai-client.ts`: connection metadata validation and SDK CVM adapter.
- `src/ai-ui.ts`: connection management, remembered/session keys, request review and reports.
- `src/connection-record.ts`: bounded credential persistence and bridge trust.
- `src/case-record.ts`: medical-text projection and validated draft serialization.
- `src/session-record.ts`: validated session/report envelope and legacy migration.
- `src/session-store.ts`: serialized autosaving, recovery and clear ordering.
- `src/draft-ui.ts`: session status, retry and clear controls.
- `bridge/provider.mjs`: server-only HTTP adapter, limits and sanitized errors.
- `bridge/server.mjs`: encrypted ContextVM MCP service and per-actor limits.

Optional domains are `storage`, `config`, `cvm`; no relay escape hatch or hard
requirement was added. No shell code or invented protocol domain is included.

## Verification boundaries

`pnpm verify` covers guidance, case/provenance tests, draft/connection validators,
server adapters, failure sanitization, timeouts, type checking and the HTML build.
`pnpm test:ai:transport` runs a real ContextVM client/server and disposable
loopback relay; it asserts encrypted envelopes and uses a synthetic HTTP response.
`pnpm test:ai:ui` uses explicit test-only SDK fixtures for the final report flow,
separate from `pnpm test:prototype <soyli-dev-url>`, which exercises the actual host.

No live vendor key, billable request, public service deployment, clinical
validation or physical-phone test is implied by these checks. Image transfer is tested with synthetic data.
The ContextVM server dependency's license discrepancy is recorded in
`THIRD_PARTY_NOTICES.md`; the napplet itself does not bundle that server dependency.

### Results, 2026-10-24

- `pnpm verify`: passed 18 tests (6 guidance, 5 original case, 7 AI/draft/adapter),
  TypeScript checking, and the single-file build.
- `pnpm test:ai:transport`: passed real encrypted handshake, background-job
  result lookup, duplicate-ID reuse, mismatched-ID rejection and actor isolation.
  Relay envelopes contained no readable dummy credentials or tool payloads.
- `pnpm test:ai:ui`: passed synthetic verification and reviewed sending, safe
  rendering, stale-report detection, sanitized failures, stopping polling,
  metadata persistence, session-key reset and a 320×560 connection dialog.
- Hosted `pnpm test:prototype`: passed actual SDK saves and draft restoration,
  original body interactions, host light/dark, absent optional capabilities,
  200×160, 320×560, 900×600, 1200×800, 1600×1000, plus emulated touch portrait and
  landscape. No browser page errors. Screenshots were visually inspected.
- `pnpm test:conformance`: **5 passed, 0 failed, 5 skipped**. Manifest evidence,
  emitted-envelope evidence and lifecycle measurements were not supplied.
- `soyli check`: passed `space-playback-6`, Chromium 153.0.8010.12.
  Final executable HTML SHA-256:
  `630447a4c57680288dbb1de7295a0e1969d73c0ae51dc13e7130bdd0a6f52621`.
- The self-contained HTML is 631,051 bytes. Static inspection found no browser
  network/storage calls, remote script/styles or bundled server SDK. Vite's
  size advisory remains expected for the Three.js renderer.
- Updated the synthetic desktop cover. Closed all owned browser/relay sessions
  and stopped the soyLI preview; its localhost listener was verified closed.

The hosted checks verify local storage/UI behavior, and separate tests verify
the client adapter and encrypted bridge transport. A real host-to-bridge-to-vendor
assessment with an actual user key remains untested. No paid request or public
deployment was made.

### Save-feedback regression, 2026-10-24

The original save completed, but its page-level toast was obscured by the native
modal's backdrop. The regression test now checks that the confirmation is
actually visible above the backdrop, rather than checking its text alone.
`pnpm test:ai:ui` also checks pending-write deduplication, validation and rejected
storage with retry, edits during a pending write, phone feedback visibility,
session-only fallback, reload persistence and exclusion of keys from storage in
that earlier build (remembered keys were added on 2026-10-25).

`pnpm verify` passed all 23 tests plus type checking and build.
Conformance passed 5 checks, skipped 5 and failed none. `soyli check` passed
`space-playback-6` for executable HTML SHA-256
`bcca1850d4c5637e2a5cb19ea06e6d5959938b119f268ffa9c888b2dbf52423c`.
Hosted `pnpm test:prototype` passed actual SDK saves with visible confirmation,
touch saving at 360×640, all four frame sizes, host light/dark and missing-domain
fallbacks, with no page errors. Desktop and phone save screenshots were inspected.
The bridge and provider code did not change; no live vendor requests were made.