@tinycloud/cli 1.5.0-beta.30 → 1.5.0-beta.32

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
@@ -245,6 +245,170 @@ current runner; an already-started action may still have happened. Keep response
245
245
  IDs as downstream idempotency keys where supported. Exactly-once external
246
246
  execution is not promised.
247
247
 
248
+ ### Prepared FORMAT1 owner import
249
+
250
+ `feed import` accepts an already-resolved FORMAT1 JSON bundle, not the raw snapshot
251
+ written by `feed export`. Supply the full target space explicitly:
252
+
253
+ ```bash
254
+ tinycloud --profile owner feed import \
255
+ --space tinycloud:pkh:eip155:1:0x2222222222222222222222222222222222222222:applications \
256
+ --file ./prepared-owner-bundle.json
257
+ ```
258
+
259
+ The authenticated owner needs SQL read/write/schema authority for
260
+ `xyz.tinycloud.tinychat/home_feed` and the registered per-agent database pairs.
261
+ The command initializes the owner archive and uses the canonical SDK to initialize
262
+ the current pair schemas. Agent response-read grants are insufficient. Import neither
263
+ registers agents nor broadens their grants.
264
+
265
+ Before writing, the importer validates versions, raw base64/SHA-256 proofs, full
266
+ source references and card/response identities. Publisher agent IDs, DIDs, and
267
+ resource pairs must match actual `feed_agents` rows and the canonical feed resource
268
+ convention, with distinct database basenames. Labels and stored provenance do not
269
+ replace signed node authority. The importer does not classify legacy data, choose
270
+ current answers, resolve recipient names, or recalculate expiry.
271
+
272
+ The complete prepared JSON value stays in owner-only
273
+ `home_feed.feed_owner_imports`, immutable under
274
+ `(source_space_id, namespace, snapshot_id)`. A declared `previousSnapshotId` must
275
+ already exist in that same scope. Reusing a snapshot ID with changed content is
276
+ refused. Raw source rows, original receipts, owner-only state and other archival
277
+ metadata are not copied to agent databases; deletion records are data, not SQL
278
+ deletes. Only envelopes declared `restoreTarget: "agent-response"` are restored
279
+ remotely, using their supplied complete canonical cards and optional private
280
+ response payloads.
281
+
282
+ Remote delivery recipients come only from explicit `runtimeTarget.agentId`
283
+ mappings. Configure `feed watch --recipient` with that same runtime ID, not a
284
+ logical recipient key, legacy name, or label. Partial mappings retain unresolved
285
+ names in the owner archive. Multiple logical keys for one runtime retain all
286
+ evidence, including conflicts; the default-deny delivery policy below still applies.
287
+ Private payloads retain the response-read boundary described above.
288
+
289
+ The archive and per-agent databases do not share a transaction. An identical
290
+ snapshot replay can finish partial progress without replacing existing content.
291
+ Replay is not exactly-once external delivery; runtime durable acceptance,
292
+ deduplication, and deadline checks remain necessary. Import does not pause source
293
+ writers, install a write fence, or establish a live-source cutover.
294
+
295
+ ### Owner SDK restore and delivery policy
296
+
297
+ The generic restore API is exported from `@tinycloud/sdk-services/feed`; the
298
+ prepared importer above uses it. Call `initializeFeedDatabases({ output, responses })`
299
+ with owner-authorized SQL handles to initialize the current pair's tables before
300
+ restoring or listening. Agents retain read-only access to responses and cannot
301
+ initialize or restore that database. Missing import/policy tables fail closed.
302
+
303
+ `restoreFeedRecords({ output, responses }, preparedRecords, { signal })` accepts
304
+ complete `FeedCard` records and response headers with optional private payloads,
305
+ source references, and minimal delivery evidence. It does not interpret legacy
306
+ records, choose current answers, or resolve recipients. Historical responses
307
+ bypass live-submission eligibility only; they still require the full published
308
+ card/revision/issuance identity. Existing live response IDs cannot be reclassified
309
+ as imported. Replays must preserve header/payload content and source bindings.
310
+ Valid literal JSON payload keys are preserved; unknown card-envelope fields are
311
+ still refused. Completed query or batch failures retain their classification if
312
+ caller cancellation races completion or later readback. Without cancellation, an
313
+ identical concurrent restore can still recover through complete content and policy readback.
314
+
315
+ Cards are stored first. Each imported response atomically stores its content-bound
316
+ claim, header, optional private payload, origins, and delivery policies in the
317
+ response database. There is no transaction across the two databases; a repeated
318
+ prepared restore can complete partial progress without replacing existing content.
319
+
320
+ Every import origin has a default `*` deny policy. Dispatch requires each origin
321
+ to have consistent evidence for the exact recipient: `state: "pending"`,
322
+ `dispatch: "resolved-pending"`, `resolvedOwnerMapping: true`, and a required
323
+ `dispatchBefore` containing a valid future timestamp or explicit `null` for no
324
+ absolute cutoff. Held, delivered, unconfirmed, refused, unknown, or conflicting
325
+ evidence does not dispatch. Missing or malformed metadata fails the page rather
326
+ than treating it as live data. Policy fields record owner decisions, not grants.
327
+ Keep raw receipts and complete legacy ledgers in owner-only storage; remote policy
328
+ rows contain only source references, state, mapping evidence, and the cutoff.
329
+
330
+ `listFeedResponseDeliveries(responses, { recipient, afterSequence, limit, signal })`
331
+ reads dispatch-eligible headers; `listFeedResponses` alone is history access, not
332
+ dispatch authority. `listenFeedResponses` applies the policy before local handoff.
333
+ Safe notification JSON carries the validated cutoff but no private payload data.
334
+ The SDK checks the cutoff before handoff and again before invoking the runner.
335
+ A queued runtime must check it immediately before acting. Expiry after a durable
336
+ claim leaves the handoff unconfirmed, without automatic resend. Runtime durable
337
+ acceptance and deduplication are still required; exactly-once external execution
338
+ is not guaranteed.
339
+
340
+ ### Card issuance history
341
+
342
+ `listFeedCardHistory(output, { afterIdentity, limit, signal })` from
343
+ `@tinycloud/sdk-services/feed` reads full stored card issuances and their payloads,
344
+ including earlier revisions and distinct issuances of the same revision.
345
+ Validation preserves literal JSON payload keys, including an own `"__proto__"` key;
346
+ it does not substitute a reconstructed payload or change its prototype.
347
+ Pages use the full `(cardId, revision, issuanceId)` cursor in SQLite BINARY order
348
+ for IDs and numeric order for revision, with default limit 100 and maximum 1000.
349
+ The result is `{ cards, nextIdentity, hasMore }`; `nextIdentity` is the last
350
+ returned full tuple, or `null` for an empty page.
351
+
352
+ This snapshot cursor is not a durable publication tail: new IDs can sort before
353
+ an existing cursor. Start each full reconciliation without `afterIdentity`.
354
+ `listFeedCards` still returns only the latest published issuance per card.
355
+ History reads use the existing output read capability and perform no DDL or writes.
356
+
357
+ ### Raw owner-only source export
358
+
359
+ `feed export` captures raw KV source bytes, not resolved Home cards or a restore plan.
360
+ Supply the full approved source space, a space-relative namespace ending in `/`,
361
+ and an unused output path. There is no installed-legacy or production default:
362
+
363
+ ```bash
364
+ tinycloud --profile owner feed export \
365
+ --source-space tinycloud:pkh:eip155:1:0x1111111111111111111111111111111111111111:applications \
366
+ --prefix example/legacy/ --out ./raw-owner-snapshot.json \
367
+ --sidecars ./raw-sidecars.json
368
+ ```
369
+
370
+ The selected profile must already hold source `tinycloud.kv/get` and explicit
371
+ `tinycloud.kv/sync` authority on that namespace. Normal per-agent SQL feed grants
372
+ do not authorize this export. The command does not broaden grants or fall back
373
+ to LIST when sync is denied.
374
+
375
+ `--sidecars` is optional. Its JSON array explicitly names every local file to
376
+ include; relative paths resolve beside that configuration file:
377
+
378
+ ```json
379
+ [
380
+ {"sourceKey":"owner/watch/hold-actions","sourceArea":"watch-state","path":"hold-actions.json"},
381
+ {"sourceKey":"owner/watch/thread","sourceArea":"thread-watch-state","path":"thread-state.json"},
382
+ {"sourceKey":"owner/expiry","sourceArea":"expiry-state","path":"expiry-state.json"}
383
+ ]
384
+ ```
385
+
386
+ Sidecar files are read as exact bytes, including malformed JSON or binary data.
387
+ No watcher is discovered or executed. Duplicate or conflicting full source keys
388
+ are refused rather than overwritten.
389
+
390
+ The owner-only versioned snapshot retains full KV keys, base64 bytes, SHA-256,
391
+ strong ETags, change metadata, tombstones, opaque cursors, and node source/authority
392
+ attestations. It bootstraps every change-feed page and verifies each binary network
393
+ read against its ETag. One empty final tail at the end cursor is required before
394
+ `completePrefixSnapshot: true` can be published. Source changes, cursor gaps,
395
+ scope drift, or mismatches fail the capture. The exporter does not restart a changed
396
+ capture or create a write fence; SDK requests retain their own transport behavior.
397
+
398
+ The complete file is fsynced and published exclusively with mode `0600`; an existing
399
+ path is never silently replaced. Keep this archive private: it can contain answers
400
+ or codes. Normal command output is only the snapshot ID, output path, and counts.
401
+ The output directory must belong to the effective user and forbid group/world
402
+ writes. Ancestors must belong to that user or root; writable shared ancestors
403
+ require sticky-bit protection of the next trusted-owned child. Unverifiable paths
404
+ are refused. Mode `0600` protects contents, not the staging pathname's identity:
405
+ the directory precondition prevents another UID from replacing that entry.
406
+ The same UID and root remain trusted OS principals, not isolated adversaries.
407
+ Validation includes every intermediate directory-alias target, not only the spelled
408
+ path and its final resolved destination.
409
+ Export does not classify records, choose current answers, resolve recipients,
410
+ evaluate legacy expiry, restore state, or dispatch pending responses.
411
+
248
412
  ## Commands
249
413
 
250
414
  | Command | Description |