@fungi.computer/oyster 0.6.0 → 0.7.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.
Files changed (163) hide show
  1. package/README.md +132 -78
  2. package/dist/agent-archive-projection.d.ts +8 -0
  3. package/dist/agent-archive-projection.js +49 -0
  4. package/dist/agent-schedules.d.ts +50 -0
  5. package/dist/agent-schedules.js +17 -0
  6. package/dist/agents.d.ts +35 -14
  7. package/dist/agents.js +93 -144
  8. package/dist/app-builds.d.ts +7 -2
  9. package/dist/app-builds.js +19 -2
  10. package/dist/approval-capability.d.ts +1 -1
  11. package/dist/approval-capability.js +11 -16
  12. package/dist/approval-home.d.ts +2 -1
  13. package/dist/approval-home.js +5 -3
  14. package/dist/apps/catalog.js +1 -1
  15. package/dist/apps/gate.d.ts +4 -7
  16. package/dist/apps/gate.js +14 -29
  17. package/dist/apps/index.d.ts +1 -1
  18. package/dist/apps/installation.d.ts +4 -42
  19. package/dist/apps/installation.js +7 -75
  20. package/dist/apps/operations.js +0 -10
  21. package/dist/apps/permissions.d.ts +18 -8
  22. package/dist/apps/permissions.js +7 -1
  23. package/dist/auth.d.ts +1 -24
  24. package/dist/auth.js +9 -39
  25. package/dist/authorization.d.ts +307 -0
  26. package/dist/authorization.js +542 -0
  27. package/dist/boot-host-script.js +1 -1
  28. package/dist/build/build-jobs.d.ts +2 -2
  29. package/dist/build/candidate-builder.d.ts +2 -0
  30. package/dist/build/candidate-builder.js +5 -1
  31. package/dist/build/candidate-cli.js +3 -0
  32. package/dist/build/candidate-cli.mjs +59 -21
  33. package/dist/build/job-log.d.ts +23 -0
  34. package/dist/build/job-log.js +88 -0
  35. package/dist/build/local-build.d.ts +2 -0
  36. package/dist/build/local-build.js +11 -9
  37. package/dist/cloudflare.d.ts +4 -1
  38. package/dist/cloudflare.js +1 -1
  39. package/dist/files.js +9 -8
  40. package/dist/git-admission.d.ts +3 -3
  41. package/dist/git-admission.js +4 -6
  42. package/dist/git-door.d.ts +8 -7
  43. package/dist/git-door.js +18 -17
  44. package/dist/git.d.ts +1 -1
  45. package/dist/host.d.ts +33 -40
  46. package/dist/host.js +153 -272
  47. package/dist/installation-execution.js +10 -5
  48. package/dist/installation-operations.d.ts +4 -180
  49. package/dist/installation-operations.js +1 -5
  50. package/dist/installation-store-generation.d.ts +4 -1
  51. package/dist/installation-store-generation.js +59 -6
  52. package/dist/internal/delivery.js +0 -1
  53. package/dist/internal/host-gate.d.ts +3 -2
  54. package/dist/internal/host-gate.js +5 -18
  55. package/dist/internal/invocation-timing.d.ts +16 -0
  56. package/dist/internal/invocation-timing.js +23 -0
  57. package/dist/internal/registry-installations.js +1 -0
  58. package/dist/invocation.d.ts +15 -14
  59. package/dist/invocation.js +58 -26
  60. package/dist/keys.d.ts +2 -19
  61. package/dist/keys.js +1 -37
  62. package/dist/mcp.d.ts +15 -27
  63. package/dist/mcp.js +105 -155
  64. package/dist/page/agent-browser.d.ts +15 -0
  65. package/dist/page/agent-browser.js +162 -0
  66. package/dist/page/app-permissions-port.d.ts +3 -0
  67. package/dist/page/app-permissions-port.js +43 -0
  68. package/dist/page/app-permissions.d.ts +11 -0
  69. package/dist/page/app-permissions.js +296 -0
  70. package/dist/page/isolated-page-theme.d.ts +2 -0
  71. package/dist/page/isolated-page-theme.js +11 -0
  72. package/dist/page/mount.d.ts +2 -13
  73. package/dist/page/mount.js +7 -93
  74. package/dist/page-script.d.ts +0 -3
  75. package/dist/page-script.js +3 -4
  76. package/dist/standalone/agent-archive-owner.d.ts +42 -0
  77. package/dist/standalone/agent-archive-owner.js +116 -0
  78. package/dist/standalone/agent-archive-schedules.d.ts +6 -0
  79. package/dist/standalone/agent-archive-schedules.js +76 -0
  80. package/dist/standalone/agent-archive.d.ts +31 -0
  81. package/dist/standalone/agent-archive.js +67 -0
  82. package/dist/standalone/agent-computers.d.ts +7 -6
  83. package/dist/standalone/agent-computers.js +4 -7
  84. package/dist/standalone/agent-mail.d.ts +2 -2
  85. package/dist/standalone/agents.d.ts +157 -20
  86. package/dist/standalone/agents.js +234 -30
  87. package/dist/standalone/app-builds-binding.js +1 -1
  88. package/dist/standalone/app-set.js +2 -54
  89. package/dist/standalone/app-source.d.ts +3 -4
  90. package/dist/standalone/app-source.js +12 -9
  91. package/dist/standalone/builds.d.ts +2 -14
  92. package/dist/standalone/builds.js +15 -13
  93. package/dist/standalone/computer-fleet.d.ts +4 -4
  94. package/dist/standalone/computer-fleet.js +13 -42
  95. package/dist/standalone/computer.d.ts +4 -3
  96. package/dist/standalone/computer.js +52 -53
  97. package/dist/standalone/dashboard-approval.d.ts +10 -0
  98. package/dist/standalone/dashboard-approval.js +66 -0
  99. package/dist/standalone/machine-link.d.ts +4 -221
  100. package/dist/standalone/machine-link.js +21 -15
  101. package/dist/standalone/media.d.ts +1 -0
  102. package/dist/standalone/media.js +20 -14
  103. package/dist/standalone/owner-routes.d.ts +16 -0
  104. package/dist/standalone/owner-routes.js +21 -0
  105. package/dist/standalone/permission-resources.d.ts +5 -0
  106. package/dist/standalone/permission-resources.js +31 -0
  107. package/dist/standalone/registry.d.ts +7 -0
  108. package/dist/standalone/registry.js +3 -0
  109. package/dist/standalone/tenancy.d.ts +0 -3
  110. package/dist/standalone/tenancy.js +2 -5
  111. package/dist/standalone/worker-routes.d.ts +19 -0
  112. package/dist/standalone/worker-routes.js +9 -0
  113. package/dist/standalone/worker.d.ts +1 -1
  114. package/dist/standalone/worker.js +27 -35
  115. package/dist/standalone/workflow-binding.d.ts +8 -3
  116. package/dist/standalone/workflow-binding.js +14 -5
  117. package/dist/standalone/workflow-git-capture.js +2 -1
  118. package/dist/standalone/workflow-host.d.ts +8 -103
  119. package/dist/standalone/workflow-host.js +1 -1
  120. package/dist/standalone/workflow-node-capture.js +2 -2
  121. package/dist/standalone/workflow-root-authority.d.ts +1 -1
  122. package/dist/standalone/workflow-root-authority.js +1 -1
  123. package/dist/standalone/workflow-schedules.d.ts +14 -5
  124. package/dist/standalone/workflow-schedules.js +11 -5
  125. package/dist/standalone/workflow-webhooks.d.ts +4 -18
  126. package/dist/standalone/workflow-webhooks.js +5 -5
  127. package/dist/standalone/workflows.d.ts +36 -28
  128. package/dist/standalone/workflows.js +6 -3
  129. package/dist/storage-frame.d.ts +6 -6
  130. package/dist/testing/capabilities.d.ts +7 -6
  131. package/dist/testing/capabilities.js +3 -6
  132. package/dist/testing/installation.d.ts +0 -1
  133. package/dist/testing/installation.js +2 -5
  134. package/dist/workers-git.d.ts +12 -4
  135. package/dist/workers-git.js +38 -35
  136. package/dist/workers.d.ts +9 -1
  137. package/dist/workers.js +44 -8
  138. package/dist/workflows.d.ts +2 -0
  139. package/dist/workflows.js +1 -0
  140. package/docs/api/README.md +7 -0
  141. package/docs/api/agent-schedules.md +175 -0
  142. package/docs/api/agents.md +34 -13
  143. package/docs/api/app-storage.md +1 -1
  144. package/docs/api/apps.md +323 -225
  145. package/docs/api/auth.md +0 -55
  146. package/docs/api/authorization.md +1300 -0
  147. package/docs/api/build.md +40 -37
  148. package/docs/api/cloudflare.md +3 -3
  149. package/docs/api/git.md +20 -11
  150. package/docs/api/host.md +64 -192
  151. package/docs/api/index.md +1 -1
  152. package/docs/api/invocation.md +14 -14
  153. package/docs/api/keys.md +1 -98
  154. package/docs/api/mcp.md +43 -40
  155. package/docs/api/modules.md +2 -0
  156. package/docs/api/page/mount.md +16 -19
  157. package/docs/api/workers-git.md +46 -12
  158. package/docs/api/workers.md +124 -235
  159. package/package.json +39 -20
  160. package/dist/page/computer-target.d.ts +0 -3
  161. package/dist/page/computer-target.js +0 -9
  162. package/dist/standalone/channel-session-door.d.ts +0 -4
  163. package/dist/standalone/channel-session-door.js +0 -72
package/README.md CHANGED
@@ -4,6 +4,12 @@ Oyster puts a desktop of Apps on your own Cloudflare account: a git platform,
4
4
  agents, a terminal, an editor, workflows and image generation, each built from a
5
5
  git commit on your own Computer.
6
6
 
7
+ The owner can read a build's status at `GET /api/builds/:jobId` and its combined
8
+ stdout/stderr at `GET /api/builds/:jobId?log`. The status diagnostic stays
9
+ within 200 characters and points to the log. The log returns base64url-encoded
10
+ raw bytes; if output exceeds 2 MiB it retains the head and tail and reports
11
+ `truncated.droppedBytes`. Both reads use the same owner authorization.
12
+
7
13
  <picture>
8
14
  <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/fungi-computer/oyster/main/docs/media/desktop-light.png">
9
15
  <img alt="The Tourtois desktop on Oyster with three Apps tiled: Trellis editing the Bonsai checks graph, Bonsai drawing a repository's network of agent forks, and Shed's terminal on the Computer" src="https://raw.githubusercontent.com/fungi-computer/oyster/main/docs/media/desktop-dark.png">
@@ -178,6 +184,42 @@ repositories and namespace. Use the configured resource names and confirm each
178
184
  is gone through Cloudflare's dashboard or API. Capture teardown errors and the
179
185
  Wrangler version rather than silently adding unrelated token permissions.
180
186
 
187
+ ### Upgrading to owner-only Oyster
188
+
189
+ Upgrading to 0.7.0 resets the installed Apps when the registry contains the
190
+ pre-owner-only `visibility`, `approvedAnonymousMethods` or `anonymousApproval`
191
+ fields. This clears all installation rows and their installation command journal
192
+ once, renews the store incarnation, and permanently records the reset time and
193
+ row count. It does not uninstall Apps through their storage-purging delete path.
194
+ Agents, Sessions, files, keys, source and release data, tenancy, approval
195
+ records and Computer state are retained.
196
+
197
+ While no App is enabled, Oyster Home shows:
198
+
199
+ > This update reset your installed Apps. Reinstall them with npx oyster
200
+ > install-apps.
201
+
202
+ Run `npx oyster install-apps` to reinstall. The notice disappears when an App is
203
+ enabled; the audit fact remains. This is not a compatibility reader or a reset
204
+ on arbitrary corruption. The separate pre-0.3 refusal below is unchanged.
205
+
206
+ Same-ID App settings: kept and back after reinstall; backend SQLite: kept but
207
+ not reachable.
208
+
209
+ Each installed App's own backend database (for example saved Notes) stays in
210
+ storage but is no longer reachable; reinstalling creates a new App Instance that
211
+ starts empty. There is no recovery for App backend data in 0.7.0; reinstalling
212
+ with `npx oyster install-apps` restores the Apps and their same-ID App settings,
213
+ and Agents, Sessions, files and keys are retained.
214
+
215
+ These are different stores. Backend SQLite belongs to the Instance's `app`
216
+ facet. Host `APP_STORAGE` settings instead belong to the installation address
217
+ plus App ID, key, scope and member membership version; the starter installer
218
+ reuses the same installation IDs, so the same App reconnects to those settings.
219
+ There is no automatic backend-data reattachment or change to either keying
220
+ scheme. Files on Computers and Agent/Session data have their own retained
221
+ owners.
222
+
181
223
  ### Upgrading from Oyster 0.2 or earlier
182
224
 
183
225
  Oyster 0.3 does not read data created by Oyster 0.1 or 0.2, and it does not
@@ -423,16 +465,16 @@ can host yourself.
423
465
 
424
466
  ![Oyster architecture: one Worker routes browsers, git clients and MCP agents to a registry, per-App Durable Objects, the git door and the Tourtois home, over R2, Artifacts and platform bindings](https://raw.githubusercontent.com/fungi-computer/oyster/main/docs/media/architecture.png)
425
467
 
426
- | Piece | Owns |
427
- | ----------------- | ----------------------------------------------------------------------------------------- |
428
- | Router | Sign-in, `/apps/<installation>/` pages, App methods, owner `/api/*` routes and `/mcp`. |
429
- | `OysterRegistry` | The installation list: which Release each installation runs, and its visibility. |
430
- | R2 | Release artifacts, stored by digest. |
431
- | App Instance | One Durable Object per installation: lifecycle, owner state and the backend facet. |
432
- | Backend facet | The App's own code and SQLite database, loaded through the Worker Loader. |
433
- | git door | Smart-HTTP `clone`, `fetch` and `push`, admitted per key and forwarded to Artifacts. |
434
- | Tourtois | The trusted shell: launcher, windows, menus and settings, rendered as the top-level page. |
435
- | Platform bindings | Workflows, Computers, Agents and Workers AI, offered only to Apps that declare them. |
468
+ | Piece | Owns |
469
+ | ----------------- | ---------------------------------------------------------------------------------------------------- |
470
+ | Router | One declared route table (`authorization.ts`): sign-in, App pages and methods, owner routes, `/mcp`. |
471
+ | `OysterRegistry` | The installation list: which Release each installation runs. |
472
+ | R2 | Release artifacts, stored by digest. |
473
+ | App Instance | One Durable Object per installation: lifecycle, owner state and the backend facet. |
474
+ | Backend facet | The App's own code and SQLite database, loaded through the Worker Loader. |
475
+ | git door | Smart-HTTP `clone`, `fetch` and `push`, admitted per key and forwarded to Artifacts. |
476
+ | Tourtois | The trusted shell: launcher, windows, menus and settings, rendered as the top-level page. |
477
+ | Platform bindings | Workflows, Computers, Agents and Workers AI, offered only to Apps that declare them. |
436
478
 
437
479
  The diagram source is
438
480
  [`docs/media/architecture.mmd`](https://github.com/fungi-computer/oyster/blob/main/docs/media/architecture.mmd).
@@ -467,31 +509,17 @@ the Computer. `examples/standalone/wrangler.jsonc` declares every binding it
467
509
  uses. Set the owner key as a secret (`OYSTER_OWNER_KEY`, 24+ characters) and
468
510
  deploy with `wrangler deploy`. Sign in at `/login`.
469
511
 
470
- Hosts may supply the optional `OYSTER_KEY_SESSIONS` binding, implementing
471
- `OysterKeySessionResolver` from `@fungi.computer/oyster/auth`. Its
472
- `resolve(value)` returns `{ keyId }` for a live host-owned session, or `null`.
473
- The host mints and expires opaque sessions and sets the `keySessionCookieName`
474
- cookie; it is not a named key's bearer secret. Hosts should use `Path=/`,
475
- `HttpOnly`, `Secure` and `SameSite=Lax` (or stricter) as defense in depth.
476
- Oyster independently checks request provenance. Cookie-only GET/HEAD navigation
477
- is allowed. Other methods require `Origin` to be present and equal the request
478
- URL's origin, and refuse `Sec-Fetch-Site: cross-site`. Missing `Origin` is
479
- refused, using the same strict predicate as the channel's POST routes. Oyster
480
- stores no browser sessions. `keySessionAuth` wraps ordinary `ownerOrKeyAuth` at
481
- the standalone tenancy composition root. Each cookie admission reopens the live
482
- key and checks its canonical owner; every authenticated door still uses that
483
- key's current grants. App pages refuse keys without App access, even when the
484
- App is public to visitors. Explicit header authentication is unchanged, and a
485
- failed Authorization header cannot fall back to a named-key cookie. The owner
486
- cookie and owner-only management API are unchanged; without the binding
487
- named-key cookies admit nobody.
512
+ Oyster is owner-only. A browser signs in only with the owner key, through
513
+ `/login` and its `SameSite=Strict` session cookie. Named keys sign in only by
514
+ header (`Authorization`), for MCP, Git and the API; no cookie admits a named
515
+ key. Anonymous requests reach only `/login`, `robots.txt` and digest-pinned
516
+ shell and App UI bytes.
488
517
 
489
518
  The celld example is a limited trial, not the full Cloudflare setup above. It
490
- requires access to Fungi's private celld fork and Botanical source repository.
491
- Use `examples/standalone/wrangler.celld.jsonc` beside `worker.ts`, not the
492
- Cloudflare-only config. The published `0.5.0` tarball does not yet contain this
493
- celld example; copy it from your authorized Botanical source checkout until a
494
- package including it is released.
519
+ requires access to Fungi's private celld fork. Use
520
+ `examples/standalone/wrangler.celld.jsonc` beside `worker.ts`, not the
521
+ Cloudflare-only config. Oyster `0.6.0` includes both files under
522
+ `examples/standalone` in the installed package.
495
523
 
496
524
  The layers it is built from stay exported for hosts that compose their own:
497
525
  `@fungi.computer/oyster/turnstile` owns Cloudflare challenge configuration and
@@ -597,7 +625,7 @@ flowchart LR
597
625
  a named key may also be the Basic password from a git credential helper. A
598
626
  Workflow Git pass (a `wgp.` Bearer header, never in a URL) goes only to
599
627
  `host.git.workflowPass`; without that port it admits nothing. Anonymous
600
- requests read only public installations and never push.
628
+ requests never clone or push.
601
629
  - **Git admission** reopens the live installation before every provider call: it
602
630
  must be enabled and its Release must declare `git.repository`. The tenant
603
631
  scope prefixes every repository name (`<installation>--<repo>` for the default
@@ -659,7 +687,9 @@ and the exact installation, repository and read/write scope it names. The last
659
687
  returns an `AdmittedGit`; the door forwards reads to `.read` and pushes to
660
688
  `.push`. Provider credentials stay inside the storage adapter. `createOyster`
661
689
  builds these ports from `host.auth`, `host.installations` and `host.git`, so
662
- most hosts set `host.git` and never call `createGitDoor` directly.
690
+ most hosts set `host.git` and never call `createGitDoor` directly. There a named
691
+ key signs Git in only for installations its own grants reach: a clone needs App
692
+ read and a push needs App use; otherwise the door refuses it.
663
693
 
664
694
  A host whose Workflow run owner issues Workflow Git credentials supplies
665
695
  `workflowGitPass` in `oysterGitRoot` or `oysterWorkerGit` options. The run owner
@@ -675,11 +705,13 @@ branches are refused before the App's push policy runs.
675
705
  ### Provide App capabilities
676
706
 
677
707
  Each `OysterCapability` supplies its Agar `id` and `version`, a mount `base`,
678
- relative Hono `routes`, an `admits(principal, installation)` decision, and
679
- `methods`. Each method descriptor has `kind`, `method` (HTTP method), and `path`
680
- (relative route). `CapabilityMethodKind` is `json | transport | read | write`:
681
- JSON calls return decoded data, transports return Fetch handles, and
682
- reads/writes return Agar's stream handles.
708
+ relative declared `routes` (a `routes()` table from
709
+ `@fungi.computer/oyster/authorization`, where every route names its requirement:
710
+ `owner`, `agent`, `computer`, `member`...), and `methods`. Each method
711
+ descriptor has `kind`, `method` (HTTP method), and `path` (relative route).
712
+ `CapabilityMethodKind` is `json | transport | read | write`: JSON calls return
713
+ decoded data, transports return Fetch handles, and reads/writes return Agar's
714
+ stream handles.
683
715
 
684
716
  Method paths may contain named parameters, such as `:agentId/sessions`. Oyster
685
717
  uses `path-to-regexp` to compile input fields into those parameters; remaining
@@ -697,10 +729,9 @@ from `@fungi.computer/oyster/files` supplies the three file contracts over the
697
729
  existing `/files/` GET, PUT and POST routes.
698
730
 
699
731
  The page offers a capability only when the installed Release declares its ID and
700
- `admits` allows the principal. Visitors receive none. Every route request also
701
- rechecks that declaration and admission using the `X-Oyster-Installation` header
702
- set by the page; a read grant does not authorize the write route sharing its
703
- file mount.
732
+ `admits` allows the principal. Every route request also rechecks that
733
+ declaration and admission using the `X-Oyster-Installation` header set by the
734
+ page; a read grant does not authorize the write route sharing its file mount.
704
735
 
705
736
  Consent approves capabilities, like OAuth scopes. An App upgrade may add a
706
737
  method that uses an already-approved capability without asking again. A new
@@ -715,10 +746,9 @@ your approval.**
715
746
  The `apps.call` permission also shows the declared target Apps and methods. Its
716
747
  route approvals retain those exact pairs: adding or renaming a target method
717
748
  needs owner approval. On Oyster these page calls run as the signed-in member
718
- through the target App's ordinary method gate; Agents, keys and visitors cannot
719
- use this path. The target receives its own grants, never the caller App's
720
- grants. The Hub shows the same consent list but does not offer this page
721
- capability.
749
+ through the target App's ordinary method gate; Agents and keys cannot use this
750
+ path. The target receives its own grants, never the caller App's grants. The Hub
751
+ shows the same consent list but does not offer this page capability.
722
752
 
723
753
  ### Connect an agent over MCP
724
754
 
@@ -743,8 +773,8 @@ operations are the ones every Computer advertises (`read`, `list`, `write`,
743
773
  The host supplies `computer(principal)` on its workspace: it returns the
744
774
  Computer's calls for a caller with Computer access and `null` otherwise. The
745
775
  standalone host gives an owner, key or member the same Computer grant its
746
- `/computer` routes use; visitors and Agents get none. The Computer accepts every
747
- call against that grant. `oysterComputerTools(host, principal)` from
776
+ `/computer` routes use; Agents get none. The Computer accepts every call against
777
+ that grant. `oysterComputerTools(host, principal)` from
748
778
  `@fungi.computer/oyster/mcp` returns this Knapsack source and its `grants`.
749
779
 
750
780
  Workers route every MCP request to the tenant's `MCP` Durable Object. Export
@@ -769,15 +799,15 @@ module's own grant is `read`. For an Agent, `access` is
769
799
  `{level, revision, grantedBy}` approval; missing it grants no tool access. For a
770
800
  named key, `access` is `{kind: "key", keyId, byInstallation}`, one
771
801
  `{level, revision, grantedBy}` per installation the key's own Ear Tag grants
772
- admit (`keyAppAccess`); an installation the key has no grant for is absent from
773
- `byInstallation` and never listed at all, not merely refused. A key's grants are
774
- set once, at `POST /api/keys`, and never edited: to change them the owner
775
- revokes the key (`POST /api/keys/revoke`) and issues a new one. A module grant
776
- does not approve an App operation. The gate also intersects the installation's
777
- explicit approvals with its verified Release; `external` methods are refused
778
- with `approval_required`. MCP uses this same projection. Capture again when tool
779
- policy, installation authority, a key's own grants or a Release changes; the
780
- source represents one immutable catalog snapshot.
802
+ admit (`appToolAccess` in `authorization.ts`); an installation the key has no
803
+ grant for is absent from `byInstallation` and never listed at all, not merely
804
+ refused. A key's grants are set once, at `POST /api/keys`, and never edited: to
805
+ change them the owner revokes the key (`POST /api/keys/revoke`) and issues a new
806
+ one. A module grant does not approve an App operation. The gate also intersects
807
+ the installation's explicit approvals with its verified Release; `external`
808
+ methods are refused with `approval_required`. MCP uses this same projection.
809
+ Capture again when tool policy, installation authority, a key's own grants or a
810
+ Release changes; the source represents one immutable catalog snapshot.
781
811
 
782
812
  ### Approve an Agent's App tools
783
813
 
@@ -980,6 +1010,20 @@ that read when host settings change. Account writes remain Hub-owned.
980
1010
 
981
1011
  ## Core concepts
982
1012
 
1013
+ ### Authorization
1014
+
1015
+ `@fungi.computer/oyster/authorization` is Oyster's one authorization owner.
1016
+ Every route is registered through `routes()` with a typed requirement
1017
+ (`publicRoute`, `door`, `owner`, `member`, `app`, `apps`, `agent`, `agents`,
1018
+ `computer`), and a table's `serve` is the only router the host builds: an
1019
+ undeclared route is not served, and a handler receives only what its requirement
1020
+ admitted. The owner reaches everything. A named key reaches exactly its Ear Tag
1021
+ grants: `apps.read`/`apps.use` per installation (including that App's Git
1022
+ repositories), `agents.use` per Agent, and `computer.use` for the Computer, its
1023
+ workspace files and media. An Agent reaches its approved App tool policy;
1024
+ nothing anonymous reaches an App. `owner("direct")` refuses App-mediated
1025
+ requests, so consent, keys and secrets never ride an App.
1026
+
983
1027
  ### Owner state and selection
984
1028
 
985
1029
  The host calls `apply` with the complete owner-issued state, then `fetch` or
@@ -1098,6 +1142,17 @@ Guest code receives only the `bindings()` its host builds, no ambient outbound
1098
1142
  access, and requests carry only `Content-Type` and the host-admitted actor,
1099
1143
  Computer and resource headers.
1100
1144
 
1145
+ One Instance shares its guest between up to eight verified foreground reads.
1146
+ Writes, raw backend requests, Gills, replay and supervisor-exclusive work run
1147
+ alone; a waiting exclusive caller holds back later reads. Every completed frame
1148
+ closes its own authority, and packets naming that finished frame refuse. Guest
1149
+ work runs only while at least one frame on that Instance remains admitted: the
1150
+ last frame stops the guest, then closes its authority, then releases admission.
1151
+ Arrivals wait while that stop is in flight. Once the opening frame's deadline
1152
+ passes, new reads wait for the cohort to drain rather than prolonging the guest.
1153
+ Revocation, expiry and a verified write preempting replay still stop it at once.
1154
+ Stopping the guest does not erase the facet's durable database.
1155
+
1101
1156
  ## API reference
1102
1157
 
1103
1158
  The core is `@fungi.computer/oyster`; the Durable Object adapter is
@@ -1317,15 +1372,14 @@ returns typed no_backend before creating a new job or starting a machine.
1317
1372
  Only an admitted Workflow-node APPS call can install its matching successful
1318
1373
  build's recorded Release at the recorded target. Ordinary App-origin
1319
1374
  installation remains refused. Existing grants may be reused, but new permissions
1320
- or anonymous exposure return needs_consent without changing the running Release.
1321
- Registering a repository as an App (**Make this an App**) is the owner's consent
1322
- for its merge Workflow to create that App's installation when none exists yet. A
1323
- Workflow-created installation is private, with no grants and no anonymous
1324
- methods. Later Workflows reuse exactly that approval and can never widen it;
1325
- every grant still needs the owner's explicit consent. The build row records the
1326
- outcome; status reads are read-only and no native alarm resumes installation.
1327
- Workflow execution owns retries. This template has no review step, review
1328
- configuration or separate consent ledger.
1375
+ return needs_consent without changing the running Release. Registering a
1376
+ repository as an App (**Make this an App**) is the owner's consent for its merge
1377
+ Workflow to create that App's installation when none exists yet. A
1378
+ Workflow-created installation has no grants. Later Workflows reuse exactly that
1379
+ approval and can never widen it; every grant still needs the owner's explicit
1380
+ consent. The build row records the outcome; status reads are read-only and no
1381
+ native alarm resumes installation. Workflow execution owns retries. This
1382
+ template has no review step, review configuration or separate consent ledger.
1329
1383
 
1330
1384
  **Breaking installation contract:** Bonsai apps.install and APPS.select require
1331
1385
  the successful build's jobId alongside installationId and release. The host
@@ -1437,8 +1491,9 @@ tenant from the request origin before sign-in and returns that tenant's
1437
1491
  or `null` to refuse an unknown origin. Oyster uses the scope for registry,
1438
1492
  workspace, keys, webhooks and repository names, and pins each Instance to its
1439
1493
  selected tenant. Canonical member roles remain distinct from owner authority.
1440
- Public App reads remain available; unknown origins and policy refusals fail
1441
- closed. `ownerOrKeyTenancy` is the single-tenant case.
1494
+ Anonymous requests reach only sign-in, `robots.txt` and the digest-pinned shell
1495
+ and App UI bytes; unknown origins and policy refusals fail closed.
1496
+ `ownerOrKeyTenancy` is the single-tenant case.
1442
1497
 
1443
1498
  ## Machine link protocol
1444
1499
 
@@ -1480,14 +1535,13 @@ The page sends an owner-only `PUT` to the same path, carrying the captured
1480
1535
  `storeGeneration`, `authorityVersion`, Release `artifactDigest`, and a bounded
1481
1536
  `{kind:"change-grants",changes:[{capability,grant}]}` command. Each grant
1482
1537
  contains `operations` and `resources`; `null` revokes that capability. Unnamed
1483
- approvals retain their existing operation and resource ceilings. Agents, keys,
1484
- visitors and App-origin requests cannot approve. A replaced store, stale
1485
- Release/version or concurrent approval returns `409`; the page refreshes the
1486
- facts and rebases touched edits for review. The required `InstallationStore`
1487
- exposes its durable `generation()` and
1488
- `compareAndPut(record, expectedVersion, expectedGeneration)`. The latter returns
1489
- a serializable commit/refusal result across Durable Object RPC and checks both
1490
- pins in the existing commit transaction.
1538
+ approvals retain their existing operation and resource ceilings. Agents, keys
1539
+ and App-origin requests cannot approve. A replaced store, stale Release/version
1540
+ or concurrent approval returns `409`; the page refreshes the facts and rebases
1541
+ touched edits for review. The required `InstallationStore` exposes its durable
1542
+ `generation()` and `compareAndPut(record, expectedVersion, expectedGeneration)`.
1543
+ The latter returns a serializable commit/refusal result across Durable Object
1544
+ RPC and checks both pins in the existing commit transaction.
1491
1545
 
1492
1546
  ## Breaking App send contract
1493
1547
 
@@ -0,0 +1,8 @@
1
+ import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
2
+ /** Revisioned, bounded-stale directory view; never the Agent's flag authority. */
3
+ export declare function agentArchiveProjection(sql: WatchdogSqliteOwner["sql"]): {
4
+ /** Only an advanced fact changes the view; replay can still be acked. */
5
+ record(agentId: string, archived: boolean, revision: number): "advanced" | "replayed" | "stale";
6
+ archived(agentId: string): boolean;
7
+ archivedIds(): ReadonlySet<string>;
8
+ };
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ const archivedRow = z.strictObject({
3
+ archived: z.union([z.literal(0), z.literal(1)]),
4
+ revision: z.number().int().nonnegative(),
5
+ });
6
+ const agentRow = z.strictObject({ agent_id: z.string() });
7
+ const changesRow = z.strictObject({ count: z.number() });
8
+ /** Revisioned, bounded-stale directory view; never the Agent's flag authority. */
9
+ export function agentArchiveProjection(sql) {
10
+ sql.exec(`CREATE TABLE IF NOT EXISTS oyster_archive_projection (
11
+ agent_id TEXT PRIMARY KEY,
12
+ archived INTEGER NOT NULL CHECK(archived IN (0, 1)),
13
+ revision INTEGER NOT NULL
14
+ ) STRICT`);
15
+ return {
16
+ /** Only an advanced fact changes the view; replay can still be acked. */
17
+ record(agentId, archived, revision) {
18
+ sql.exec(`INSERT INTO oyster_archive_projection(agent_id,archived,revision)
19
+ VALUES(?,?,?)
20
+ ON CONFLICT(agent_id) DO UPDATE SET
21
+ archived=excluded.archived, revision=excluded.revision
22
+ WHERE oyster_archive_projection.revision < excluded.revision`, agentId, archived ? 1 : 0, revision);
23
+ if (changesRow.parse(sql.exec("SELECT changes() AS count").toArray()[0])
24
+ .count > 0)
25
+ return "advanced";
26
+ const row = sql
27
+ .exec("SELECT archived, revision FROM oyster_archive_projection WHERE agent_id=?", agentId)
28
+ .toArray()[0];
29
+ const current = row === undefined ? null : archivedRow.parse(row);
30
+ return current !== null &&
31
+ current.revision === revision &&
32
+ current.archived === (archived ? 1 : 0)
33
+ ? "replayed"
34
+ : "stale";
35
+ },
36
+ archived(agentId) {
37
+ const row = sql
38
+ .exec("SELECT archived, revision FROM oyster_archive_projection WHERE agent_id=?", agentId)
39
+ .toArray()[0];
40
+ return row === undefined ? false : archivedRow.parse(row).archived === 1;
41
+ },
42
+ archivedIds() {
43
+ return new Set(sql
44
+ .exec("SELECT agent_id FROM oyster_archive_projection WHERE archived=1")
45
+ .toArray()
46
+ .map((row) => agentRow.parse(row).agent_id));
47
+ },
48
+ };
49
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ export { agentSchedulePageSchema, agentScheduleControlReceiptSchema, } from "@fungi.computer/shiitake-extensions/schedules";
3
+ export type { AgentSchedulePage, AgentScheduleTarget, AgentScheduleControlReceipt, } from "@fungi.computer/shiitake-extensions/schedules";
4
+ /** The App selects one Agent; its host must admit that target before opening it. */
5
+ export declare const appAgentScheduleListInputSchema: z.ZodObject<{
6
+ after: z.ZodOptional<z.ZodObject<{
7
+ agentId: z.ZodString;
8
+ kind: z.ZodUnion<readonly [z.ZodLiteral<"almanac">, z.ZodLiteral<"loop">]>;
9
+ after: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strict>>;
11
+ limit: z.ZodDefault<z.ZodNumber>;
12
+ agentId: z.ZodString;
13
+ }, z.core.$strict>;
14
+ /** A single bounded native read, not an all-Agent inventory grant. */
15
+ export type AppAgentScheduleListInput = z.input<typeof appAgentScheduleListInputSchema>;
16
+ /** Both hosts decode the ordinary AGENTS packet with the native owner's grammar. */
17
+ export declare const agentScheduleBindingArgs: {
18
+ /** One bounded content-bearing read, admitted independently of manage. */
19
+ schedules: z.ZodTuple<[z.ZodObject<{
20
+ after: z.ZodOptional<z.ZodObject<{
21
+ agentId: z.ZodString;
22
+ kind: z.ZodUnion<readonly [z.ZodLiteral<"almanac">, z.ZodLiteral<"loop">]>;
23
+ after: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$strict>>;
25
+ limit: z.ZodDefault<z.ZodNumber>;
26
+ agentId: z.ZodString;
27
+ }, z.core.$strict>], null>;
28
+ /** Pause the exact listed schedule, retaining future resume and accepted work. */
29
+ pauseSchedule: z.ZodTuple<[z.ZodDiscriminatedUnion<[z.ZodObject<{
30
+ kind: z.ZodLiteral<"almanac">;
31
+ agentId: z.ZodString;
32
+ scheduleId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
33
+ }, z.core.$strict>, z.ZodObject<{
34
+ kind: z.ZodLiteral<"loop">;
35
+ agentId: z.ZodString;
36
+ sessionId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
37
+ loopId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
38
+ }, z.core.$strict>], "kind">], null>;
39
+ /** Cancel future occurrences of the exact identity without erasing history. */
40
+ deleteSchedule: z.ZodTuple<[z.ZodDiscriminatedUnion<[z.ZodObject<{
41
+ kind: z.ZodLiteral<"almanac">;
42
+ agentId: z.ZodString;
43
+ scheduleId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
44
+ }, z.core.$strict>, z.ZodObject<{
45
+ kind: z.ZodLiteral<"loop">;
46
+ agentId: z.ZodString;
47
+ sessionId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
48
+ loopId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
49
+ }, z.core.$strict>], "kind">], null>;
50
+ };
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import { agentScheduleListInputSchema, agentScheduleTargetSchema, } from "@fungi.computer/shiitake-extensions/schedules";
3
+ export { agentSchedulePageSchema, agentScheduleControlReceiptSchema, } from "@fungi.computer/shiitake-extensions/schedules";
4
+ /** The App selects one Agent; its host must admit that target before opening it. */
5
+ export const appAgentScheduleListInputSchema = agentScheduleListInputSchema.extend({
6
+ /** The native directory identity whose owner the host must admit. */
7
+ agentId: z.string().min(1),
8
+ });
9
+ /** Both hosts decode the ordinary AGENTS packet with the native owner's grammar. */
10
+ export const agentScheduleBindingArgs = {
11
+ /** One bounded content-bearing read, admitted independently of manage. */
12
+ schedules: z.tuple([appAgentScheduleListInputSchema]),
13
+ /** Pause the exact listed schedule, retaining future resume and accepted work. */
14
+ pauseSchedule: z.tuple([agentScheduleTargetSchema]),
15
+ /** Cancel future occurrences of the exact identity without erasing history. */
16
+ deleteSchedule: z.tuple([agentScheduleTargetSchema]),
17
+ };
package/dist/agents.d.ts CHANGED
@@ -6,11 +6,11 @@
6
6
  * Shiitake's HTTP routes (`/sessions…`, `/acp`) and Knapsack's Session browser
7
7
  * transport (`/browser/…`), served by the adapter behind the same App grants.
8
8
  */
9
- import { Hono } from "hono";
10
- import { type LiveOysterKey } from "./keys.js";
9
+ import type { TenantPrincipal } from "./tenant.js";
10
+ import { type Routes } from "./authorization.js";
11
11
  import type { AppToolPolicy } from "./apps/tools.js";
12
12
  import * as Schema from "effect/Schema";
13
- import { type OysterCapability, type OysterEnv, type OysterPrincipal } from "./host.js";
13
+ import { type OysterCapability, type OysterEnv } from "./host.js";
14
14
  import type { HostActor } from "@fungi.computer/puffball/app";
15
15
  import type { Avatar } from "@fungi.computer/sprite/avatar";
16
16
  import type { AgentModelChoice, AgentModelRefusal } from "./standalone/agent-model-choice.js";
@@ -73,21 +73,42 @@ export type AgentToolPolicyStatus = Readonly<{
73
73
  status: "pending" | "approved";
74
74
  revision: string;
75
75
  }>;
76
- /** Where an Oyster's agents live and run. */
76
+ /**
77
+ * Where an Oyster's agents live and run. Archive is non-destructive: `list()`
78
+ * hides archived agents, and `list({ archived: true })` shows only archives.
79
+ * Archived agents keep existing reads, replays and settlements, but refuse new
80
+ * Sessions, prompts and steers with `contract/archived`. Archive refuses
81
+ * non-idle Session work or unsettled delegations; it pauses Almanac and Loop
82
+ * schedules and restores only archive-paused schedules on unarchive.
83
+ */
77
84
  export type OysterAgents = Readonly<{
78
- /** Every agent, oldest first. */
79
- list(): Promise<readonly OysterAgent[]>;
85
+ /** Active agents by default; archived agents only by explicit selection. */
86
+ list(options?: Readonly<{
87
+ archived?: boolean;
88
+ }>): Promise<readonly OysterAgent[]>;
89
+ /** Owner-only HTTP transition; creator Agents use their own control tool. */
90
+ setArchived(agentId: string, archived: boolean, principal: TenantPrincipal): Promise<Readonly<{
91
+ archived: boolean;
92
+ revision: number;
93
+ }> | Readonly<{
94
+ error: "not_found" | "not_granted" | "self" | "not_creator" | "running" | "pending" | "unreadable" | "archived";
95
+ /** Safe refusal text; busy work names its command. */
96
+ message?: string;
97
+ /** A repair failure can follow an already-committed owner flag. */
98
+ archived?: boolean;
99
+ revision?: number;
100
+ }>>;
80
101
  /**
81
102
  * Create an agent; retrying with the same `requestId` returns the same one.
82
103
  * With no `avatar` it wears its seeded default until the owner picks one.
83
104
  */
84
- create(name: string, requestId: string, principal: OysterPrincipal, actor: AgentPolicyActor, avatar?: Avatar): Promise<OysterAgent>;
105
+ create(name: string, requestId: string, principal: TenantPrincipal, actor: AgentPolicyActor, avatar?: Avatar): Promise<OysterAgent>;
85
106
  /** Owner-only rename; refuses a name already held by another Agent. */
86
- rename(agentId: string, name: string, principal: OysterPrincipal): Promise<OysterAgent | Readonly<{
107
+ rename(agentId: string, name: string, principal: TenantPrincipal): Promise<OysterAgent | Readonly<{
87
108
  error: "not_found" | "name_taken" | "not_granted";
88
109
  }>>;
89
110
  /** Owner-only avatar change for an existing Agent. */
90
- setAvatar(agentId: string, avatar: Avatar, principal: OysterPrincipal): Promise<OysterAgent | Readonly<{
111
+ setAvatar(agentId: string, avatar: Avatar, principal: TenantPrincipal): Promise<OysterAgent | Readonly<{
91
112
  error: "not_found" | "not_granted";
92
113
  }>>;
93
114
  /** Owner-only, secret-free Agent default; an unknown Agent cannot be configured. */
@@ -95,7 +116,7 @@ export type OysterAgents = Readonly<{
95
116
  /** Parse the owner's JSON through the deployable Agent's model preference schema. */
96
117
  decodeModelChoice(json: string): AgentModelChoice | null;
97
118
  /** Persist a supported model and source reference for this Agent, or return an explicit refusal. */
98
- setModelChoice(agentId: string, choice: AgentModelChoice, principal: OysterPrincipal): Promise<AgentModelChoice | Readonly<{
119
+ setModelChoice(agentId: string, choice: AgentModelChoice, principal: TenantPrincipal): Promise<AgentModelChoice | Readonly<{
99
120
  error: "not_found" | "not_granted" | AgentModelRefusal;
100
121
  }>>;
101
122
  /** This Agent's App-tool policy status, or `null` for no such Agent. */
@@ -107,7 +128,7 @@ export type OysterAgents = Readonly<{
107
128
  /** Read the current revision for direct owner approval. */
108
129
  policyRevision(agentId: string): Promise<string | null>;
109
130
  /** Replace an Agent's tool approval under its authenticated attaching human. */
110
- approveAppToolPolicy(agentId: string, principal: OysterPrincipal, level: AppToolPolicy["level"], expectedRevision: string | null, actor: AgentPolicyActor): Promise<AppToolPolicy | PendingAgentPolicy | Readonly<{
131
+ approveAppToolPolicy(agentId: string, principal: TenantPrincipal, level: AppToolPolicy["level"], expectedRevision: string | null, actor: AgentPolicyActor): Promise<AppToolPolicy | PendingAgentPolicy | Readonly<{
111
132
  error: "not_found" | "revision_conflict" | "not_granted";
112
133
  }>>;
113
134
  /** An Agent's connected MCP servers, or `null` when there is no such Agent. */
@@ -126,11 +147,11 @@ export type OysterAgents = Readonly<{
126
147
  visit(agentId: string, sessionId: string): Promise<boolean>;
127
148
  }>;
128
149
  /** Agent adapters admitted against each App's declared capabilities. */
129
- export declare function agentCapabilities(agents: OysterAgents, liveKey: (keyId: string) => Promise<LiveOysterKey | null>): readonly OysterCapability[];
150
+ export declare function agentCapabilities(agents: OysterAgents): readonly OysterCapability[];
130
151
  /** Host-owned direct approval under the admitted owner; never an App capability. */
131
- export declare function agentApprovalRoutes(agents: OysterAgents): Hono<OysterEnv>;
152
+ export declare function agentApprovalRoutes(agents: OysterAgents): Routes<OysterEnv>;
132
153
  /**
133
154
  * Host-owned Agent MCP server custody for the owner directly; never an App
134
155
  * capability. Bearer tokens go in and never come back out.
135
156
  */
136
- export declare function agentMcpServerRoutes(agents: OysterAgents): Hono<OysterEnv>;
157
+ export declare function agentMcpServerRoutes(agents: OysterAgents): Routes<OysterEnv>;