@kehto/paja 0.17.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,15 @@ Any dev server works as long as it answers `Origin: null` with
57
57
  and logs a `paja.target.cors.error` entry in the message log, plus a console
58
58
  warning, when the target would block the sandboxed frame.
59
59
 
60
+ Every drawer section is a flush, independent native accordion: **Pointer** (in
61
+ runtime-pointer mode), **Interfaces**, **ACL**, **Signer**, **Resource servers**,
62
+ then **Messages** last. Headers support Enter/Space, visible keyboard focus and
63
+ state chevrons; bodies keep their padding and live controls when hidden. Sections
64
+ start expanded unless a validated origin-local boolean preference says otherwise.
65
+ The collapsed map uses `kehto:paja:sidebar-sections:v1`, separately from whole-drawer
66
+ visibility and resource server values. Storage failures leave session-only toggles
67
+ usable; collapsing never reloads the running app or discards unsaved drafts.
68
+
60
69
  The console shows supported interfaces with per-domain injection toggles,
61
70
  runtime ACL controls, signer controls, and a filterable message log with visible
62
71
  error details. It starts expanded and collapses to the left with the chevron
@@ -193,6 +202,34 @@ and hinted-author/publisher server lists first, then the active shell user's
193
202
  published list, the current window's verified pointer-manifest servers, and
194
203
  finally configured runtime fallbacks.
195
204
 
205
+ ## Local `index.html` files
206
+
207
+ In runtime-pointer mode, Paja can also open a napplet straight from disk. Use
208
+ **Open file…** next to **Load**, or drop an `index.html` anywhere on the Paja
209
+ page (not onto a running napplet frame, which receives its own drag events).
210
+ The file opens in a new runtime tab named after the file.
211
+
212
+ - **Identity comes from the bytes.** Paja hashes the exact file bytes (`sha256`)
213
+ and derives `aggregateHash` as the NIP-5A aggregate over the single
214
+ `/index.html` path entry. That is the same derivation the resolver checks for a
215
+ published single-file napplet, so editing the file gives it a new identity. The
216
+ `dTag` comes from the NIP-5D publishing metadata
217
+ `<meta name="napplet-id" content="…">`. Without it, Paja uses
218
+ `local-<file-stem>`.
219
+ - **Same loading path as verified pointers.** Paja registers the identity before
220
+ the frame runs. It injects the Class-1 CSP (with `connect-src 'none'`, since a
221
+ local file has no relay or Blossom hints) and then the runtime-owned
222
+ `window.napplet` prelude, including mandatory `shell`. The bytes go in through
223
+ `srcdoc` under the same `allow-scripts` sandbox.
224
+ - **Development only and unverified.** A local file has no signed manifest. It
225
+ never enters the installed napplet catalog, never becomes an intent delivery
226
+ target, has no share link, and is not restored after a page reload.
227
+ - **Self-contained single-file HTML only.** A `srcdoc` document has no base URL,
228
+ and the CSP denies network loads, so relative `<script src>`, stylesheets, and
229
+ images do not load. Paja lists relative references it finds in the status line
230
+ and the message log. Build with an inlining bundler (for example
231
+ `vite-plugin-singlefile`). Opening a folder or zip of assets is not supported.
232
+
196
233
  ## Installed intent handlers and delivery
197
234
 
198
235
  Paja keeps resolver-verified pointer and manifest facts in an installed catalog,
@@ -201,7 +238,10 @@ the catalog record; an explicit artifact removal removes it. Closing, reloading,
201
238
  or replacing a frame never makes an installed handler unavailable, so a cold
202
239
  target can still be selected and started later.
203
240
 
204
- Intent selection considers only exact compatible contracts from that catalog.
241
+ Intent selection considers only exact compatible contracts from that catalog. Required or optional INC declarations are eligible only when
242
+ INC is available in the target's host-resolved environment. Missing optional INC
243
+ still permits frame loading. Nameless root/snapshot artifacts may also run, but
244
+ are excluded from the dTag-keyed intent catalog.
205
245
  Paja can use a compatible user default, ask its host chooser when more than one
206
246
  candidate is available, or reject an ambiguity. An explicit handler d-tag is
207
247
  accepted only when it is an installed compatible handler and the invoking sender
@@ -211,7 +251,7 @@ arbitrary running frame.
211
251
  When Paja receives an invocation, it selects and opens or reuses a verified
212
252
  target. The controller waits for the target generation's
213
253
  registered `MessageEvent.source` to establish its real `shell.ready` session;
214
- it checks that generation is still current, sends one target-only `inc.event`
254
+ it checks that generation is still current, sends one target-only `intent.deliver`
215
255
  with the selected queryless convention, and returns the final handled target
216
256
  identity. A superseded target/source, failed open/readiness, or terminal send is
217
257
  handled by the controller's replacement/retry/terminal policy and produces a
@@ -233,16 +273,31 @@ provides local `ready()`, `supports()`, read-only `services`, and one-shot
233
273
  `onReady()`. This is the documented upstream-package-drift exception, not a
234
274
  shim capability.
235
275
 
236
- Before Paja assigns a verified runtime-pointer document to `srcdoc`, it inserts
237
- Kehto's local Class-1 CSP before the host-owned namespace prelude. The policy
238
- denies all defaults; permits inline script/style, WebAssembly compilation through
239
- the narrow `'wasm-unsafe-eval'` source, `data:`/`blob:` images, and `data:` fonts;
240
- keeps JavaScript string evaluation blocked; grants `connect-src` only to the resolved relay and Blossom
241
- origins; explicitly denies worker, child, frame, media, object, manifest,
242
- prefetch, base, and form capabilities; and ends with `frame-ancestors 'self'`.
243
- The NIP-5D verified-srcdoc and opaque-sandbox rules do not mandate this CSP;
244
- it is Kehto policy. Local target-URL authoring mode is intentionally outside
245
- this verified-artifact policy path.
276
+ Before Paja assigns a verified runtime-pointer document to `srcdoc`, it calls
277
+ `@kehto/shell`'s `prepareNappletSrcdoc`. The shell inserts validated CSP first,
278
+ then the mandatory namespace. Defaults permit inline script/style, WASM byte
279
+ compilation, `data:`/`blob:` images and `data:` fonts, and deny other resource
280
+ classes. Paja grants direct connections only to resolved relay and Blossom
281
+ origins plus explicit host grants.
282
+
283
+ Hosts can pass `csp` to `createPajaRuntimeHostConfig`:
284
+
285
+ ```ts
286
+ const config = createPajaRuntimeHostConfig({
287
+ pointer,
288
+ csp: {
289
+ connectOrigins: ['https://api.example'],
290
+ directives: { 'media-src': ['blob:'], 'img-src': ['data:'] },
291
+ },
292
+ });
293
+ ```
294
+
295
+ The shell rejects broad JavaScript evaluation, non-origin connection grants,
296
+ and script policies that block its bootstrap. NIP-5D's CSP recommendations are
297
+ enforced; individual baseline source lists remain configurable. Existing
298
+ artifact CSP is retained. Set `frame-ancestors` on the host HTTP response, not
299
+ in a meta policy. See the [shared policy contract](https://github.com/kehto/web/blob/main/docs/policies/NIP-5D-CONFORMANCE.md#shell-csp-enforcement).
300
+ Local target-URL authoring mode retains its separate development policy and HMR.
246
301
 
247
302
  Environment simulation can be supplied through CLI flags or a JSON config file:
248
303
 
@@ -318,6 +373,25 @@ pubkey without `signEvent` is read-only. This implements the draft
318
373
 
319
374
  ## NAP-RESOURCE schemes
320
375
 
376
+ The development sidebar's **Resource servers** textarea adds read-only Blossom
377
+ lookup origins in both target-URL and runtime-pointer modes. Enter one public
378
+ HTTPS origin per line (`cdn.example` is shorthand for `https://cdn.example`)
379
+ and press **Save**. Blanks are ignored and equivalent origins are deduplicated
380
+ in first-seen order. A bad line rejects the whole save without changing the
381
+ active or saved list; credentials, paths, queries, fragments, HTTP and obvious
382
+ local/private hosts are rejected. Saving blank clears only these extras.
383
+
384
+ Valid saves apply to subsequent Blossom requests across all running tabs,
385
+ without reloading their frames. The list persists in the host origin's
386
+ `localStorage`; if storage fails, it remains usable **session-only**, and the
387
+ status warns that an older saved list may return after reload. Extras are
388
+ appended after existing request/event/user/window/configured defaults. The
389
+ combined eight-server cap remains: extras may not be reached if earlier
390
+ candidates fill it. This setting does not change upload destinations,
391
+ per-napplet NAP-CONFIG, initial pointer/artifact loading, direct HTTP(S) resource
392
+ URLs, or napplet CSP/network grants. Browser CORS and the DNS limitation below
393
+ still apply.
394
+
321
395
  Paja's developer-runtime policy accepts arbitrary `http:` and `https:` resource
322
396
  URLs so a normal remote image does not look broken merely because its origin was
323
397
  not pre-granted. Paja resolves those URLs with browser `fetch`, omits credentials
@@ -325,33 +399,68 @@ and referrer data, caps responses at 10 MiB, and classifies MIME from returned
325
399
  bytes. Its byte classifier recognizes checksum-valid Game Boy ROM headers as
326
400
  `application/vnd.nintendo.gb-rom`; it never trusts a server-supplied media type.
327
401
  Browser network and CORS rules still apply: an unreadable response is the
328
- canonical `network-error`, while any CORS-readable response is returned as NAP
329
- bytes.
402
+ canonical `network-error`. HTTP(S) and locally decoded `data:` bytes must have a
403
+ recognized type; opaque invalid-UTF-8 or NUL-bearing binary remains rejected.
404
+ Only capped, SHA-256-verified canonical Blossom resources may return unknown
405
+ opaque bytes as `application/octet-stream`. Recognized formats retain their
406
+ sniffed MIME. SVG, HTML, XML and script prefixes remain blocked, even with NUL or
407
+ invalid-UTF-8 suffixes; upstream headers never override classification.
408
+ Byte-identifiable UTF-16 markup is checked by decoding the complete capped
409
+ buffer; identifiable UTF-32 document signatures are conservatively rejected.
410
+ This encoding guard is not a general markup parser.
330
411
 
331
412
  `data:` remains locally decoded. `blossom:` is a separate, content-addressed
332
413
  boundary and is advertised because each request may provide server locations
333
- without a host default. Paja accepts only public-looking HTTPS origin hints,
334
- discards invalid/private literals, and deduplicates them. For a canonical URL
414
+ without a host default. For example, reads accept
415
+ `blossom:<hash>.gbc?xs=cdn.example&as=<pubkey>&sz=32768` and
416
+ `blossom:sha256:<hash>.gbc?xs=cdn.example&sz=32768`.
417
+ Repeated `xs` and `as` are supported without an observed event. Paja accepts only
418
+ public-looking HTTPS origin hints (scheme-less domains use HTTPS), discards
419
+ invalid/private literals, and deduplicates them. Unlike BUD-10's suggested HTTP
420
+ retry, URI hints never enable public HTTP. For a Blossom URL
335
421
  previously returned to the same napplet window by `outbox.getEvent`,
336
422
  `outbox.query`, or `outbox.subscribe`, Paja retains bounded event context
337
- without prefetching bytes. Resolution tries request and
338
- event-local server hints first, then lazily queries hinted authors' and the
423
+ without prefetching bytes. Resolution tries explicit request servers, URI `xs`,
424
+ and event-local server hints first, then lazily queries URI `as`, event-hinted authors' and the
339
425
  event publisher's newest BUD-03 kind `10063` lists through the verified
340
426
  NIP-65-aware OUTBOX router, then queries the active shell user's BUD-03 list
341
427
  through that same router, then uses the current window's verified
342
- pointer-manifest servers, followed by upload-runtime fallbacks. The user-list
428
+ pointer-manifest servers, followed by upload-runtime fallbacks and saved extra
429
+ resource servers. The user-list
343
430
  lookup works independently of upload mode; an upload runtime may reuse the same
344
431
  servers when present. ROM-specific event and publisher locations retain
345
- priority over the user/runtime fallbacks. The combined list is capped at eight
346
- candidates. The only accepted identifier is `blossom:sha256:<hex>`;
347
- Paja refuses redirects, verifies the requested SHA-256, and permits plain-HTTP
432
+ priority over the user/runtime fallbacks. URI length is capped at 8192 characters;
433
+ retained unique `xs`, the complete author lookup list, and combined server
434
+ candidates are each capped at eight. Both prefixes require exactly 64 hex
435
+ characters, with an optional single ASCII-alphanumeric extension preserved in
436
+ `GET /<hash>.<ext>`; no query hints reach the blob server. Extensions and upstream
437
+ headers never establish MIME. Fragments, path injection, malformed escapes/authors,
438
+ and invalid or repeated `sz` fail with `invalid-request` before discovery.
439
+ `sz` must be a positive decimal safe integer; sizes above 10 MiB fail `too-large`.
440
+ Paja checks a numeric Content-Length before reading and always checks actual
441
+ bytes against `sz`; a size or hash mismatch is `decode-failed` unless fallback succeeds.
442
+ Hash-only event context stays window-scoped, without transferring extension/size
443
+ or retaining request hints. Byte caches retain the original URL and identity scope;
444
+ public author lists reuse five-minute cache/single-flight lookups, with incomplete misses retryable.
445
+
446
+ [BUD-10 at `b5bd280`](https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/10.md)
447
+ requires lowercase hashes and an extension (default `.bin`) for emitted references.
448
+ Extensionless reads, mixed-case hashes, and the `sha256:` alias are deliberately
449
+ retained local compatibility, not a full BUD-10 conformance claim. Upload URI
450
+ generation is unchanged. Paja refuses redirects, verifies SHA-256 and byte MIME,
451
+ and permits plain-HTTP
348
452
  transport only for configured loopback development defaults. Browser-only Paja
349
453
  cannot pin DNS results, so production runtimes must add the draft's DNS-time
350
454
  private-address checks. The current
351
455
  [NAP-RESOURCE draft at `fa6bcc6`](https://github.com/napplet/naps/blob/fa6bcc6935aa19e7b70ab2a2c721dafca77c78e1/naps/NAP-RESOURCE.md)
352
- assigns fetching and policy to the runtime and defines no wire-level Blossom
353
- server-hint field. Retaining verified manifest servers per window is therefore
354
- Paja host policy, informed by the current
456
+ was rechecked for this MIME policy against PR #80's exact head. The opaque
457
+ fallback preserves byte sniffing, hash verification, complete Blob delivery,
458
+ ordered independent bulk results, and the prohibition on raw SVG delivery.
459
+ This is bounded conformance, not full draft compliance: existing developer HTTP
460
+ policy, DNS enforcement, SVG rejection instead of rasterization, and request
461
+ server-hint differences remain outside this change. The draft defines no
462
+ wire-level Blossom server-hint field. Retaining verified manifest servers per
463
+ window is therefore Paja host policy, informed by the current
355
464
  [NIP-5D draft at `24711d9`](https://github.com/nostr-protocol/nips/blob/24711d9c47bbdd07908bf1d52bf677d9cbc530f0/5D.md),
356
465
  which defines manifest `server` tags. The existing request-server compatibility
357
466
  path comes from the earlier NAP-RESOURCE draft at `9511232`, with the merged package implementation
@@ -364,3 +473,13 @@ Full package docs: [`docs/packages/paja.md`](../../docs/packages/paja.md).
364
473
  Getting started: [`docs/how-tos/paja-getting-started.md`](../../docs/how-tos/paja-getting-started.md).
365
474
  Local authoring how-to: [`docs/how-tos/paja-local-authoring.md`](../../docs/how-tos/paja-local-authoring.md).
366
475
  Generated API module: `docs/api/modules/_kehto_paja.html` (run `pnpm docs:api`).
476
+
477
+ ## NIP-5D event compatibility
478
+
479
+ Current manifests use a direct artifact `x` hash, plain-text `content`, independent
480
+ `z`/`i` routing declarations, and required `R` / optional `O` domains. Kehto also
481
+ accepts legacy aggregate events through an isolated compatibility adapter.
482
+ Existing `aggregateHash` host/cache/ACL fields carry the verified artifact hash
483
+ for current events; legacy identities keep their original aggregate. Both paths
484
+ verify signatures and bytes before runtime injection and `srcdoc` execution.
485
+ For the schema and removal boundary, see [event migration](https://kehto.github.io/web/docs/migrations/NIP-5D-EVENT-SCHEMA.html).