sealkeep 0.9.0 → 0.11.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 (274) hide show
  1. package/ARCHITECTURE.md +163 -14
  2. package/CHANGELOG.md +223 -1
  3. package/CONTROL_PLANE.md +2 -2
  4. package/LICENSE +1 -1
  5. package/README.md +108 -22
  6. package/THIRD_PARTY.md +2 -2
  7. package/THREAT_MODEL.md +23 -4
  8. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/chunk-access.d.ts +26 -4
  9. package/dist/packages/sealkeep-crypto/src/chunk-access.js +219 -0
  10. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.d.ts +11 -1
  11. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.js +58 -21
  12. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.d.ts +2 -2
  13. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.d.ts +1 -0
  14. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.js +1 -0
  15. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.js +4 -4
  16. package/dist/packages/sealkeep-crypto/src/squeeze.d.ts +6 -0
  17. package/dist/packages/sealkeep-crypto/src/squeeze.js +39 -0
  18. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.d.ts +36 -3
  19. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.js +196 -37
  20. package/dist/site/index.html +1808 -1904
  21. package/dist/site/llms.txt +67 -0
  22. package/dist/site/trust/architecture-data-flow.html +53 -0
  23. package/dist/site/trust/audit-roadmap.html +37 -0
  24. package/dist/site/trust/deployment-responsibility.html +11 -0
  25. package/dist/site/trust/dpa-sample.html +30 -0
  26. package/dist/site/trust/release-provenance.html +21 -0
  27. package/dist/site/trust/subprocessors.html +15 -0
  28. package/dist/site/trust/threat-model.html +34 -0
  29. package/dist/site/trust/toms.html +41 -0
  30. package/dist/site/trust-document.css +32 -0
  31. package/dist/site/trust.html +73 -0
  32. package/dist/site/visual/assets/index-8Nxnkc7o.js +207 -0
  33. package/dist/site/visual/assets/index-oxLy1bpy.css +1 -0
  34. package/dist/site/visual/index.html +18 -0
  35. package/dist/site.zip +0 -0
  36. package/dist/src/activity.d.ts +9 -0
  37. package/dist/src/activity.js +90 -1
  38. package/dist/src/adapters.d.ts +175 -5
  39. package/dist/src/adapters.js +961 -73
  40. package/dist/src/agent-context.d.ts +135 -0
  41. package/dist/src/agent-context.js +1059 -0
  42. package/dist/src/archive-copies.d.ts +47 -0
  43. package/dist/src/archive-copies.js +179 -0
  44. package/dist/src/audit.d.ts +1 -1
  45. package/dist/src/audit.js +29 -4
  46. package/dist/src/autopilot.d.ts +52 -7
  47. package/dist/src/autopilot.js +143 -25
  48. package/dist/src/background-bandwidth.d.ts +46 -0
  49. package/dist/src/background-bandwidth.js +301 -0
  50. package/dist/src/background-cpu.d.ts +82 -0
  51. package/dist/src/background-cpu.js +212 -0
  52. package/dist/src/background-worker-error.d.ts +12 -0
  53. package/dist/src/background-worker-error.js +18 -0
  54. package/dist/src/branding.d.ts +24 -1
  55. package/dist/src/branding.js +31 -1
  56. package/dist/src/bridge.d.ts +233 -0
  57. package/dist/src/bridge.js +604 -0
  58. package/dist/src/byte-stream.d.ts +91 -0
  59. package/dist/src/byte-stream.js +385 -0
  60. package/dist/src/chunk-store.d.ts +41 -8
  61. package/dist/src/chunk-store.js +161 -65
  62. package/dist/src/cli.js +1698 -163
  63. package/dist/src/cloud.d.ts +841 -31
  64. package/dist/src/cloud.js +3196 -277
  65. package/dist/src/context-background.d.ts +37 -0
  66. package/dist/src/context-background.js +309 -0
  67. package/dist/src/context-drain-child.d.ts +1 -0
  68. package/dist/src/context-drain-child.js +98 -0
  69. package/dist/src/context-reader.d.ts +118 -0
  70. package/dist/src/context-reader.js +447 -0
  71. package/dist/src/control-plane/auth.d.ts +32 -4
  72. package/dist/src/control-plane/auth.js +85 -24
  73. package/dist/src/control-plane/server.js +19 -6
  74. package/dist/src/control-plane.d.ts +17 -1
  75. package/dist/src/control-plane.js +32 -6
  76. package/dist/src/crypto.d.ts +1 -1
  77. package/dist/src/crypto.js +5 -5
  78. package/dist/src/daemon-lease.d.ts +70 -0
  79. package/dist/src/daemon-lease.js +420 -0
  80. package/dist/src/daemon.d.ts +94 -1
  81. package/dist/src/daemon.js +1082 -105
  82. package/dist/src/darwin-service-policy.d.ts +41 -0
  83. package/dist/src/darwin-service-policy.js +60 -0
  84. package/dist/src/dashboard-cli.js +15 -15
  85. package/dist/src/device-authorization.d.ts +37 -0
  86. package/dist/src/device-authorization.js +199 -0
  87. package/dist/src/device-enrollment.d.ts +91 -0
  88. package/dist/src/device-enrollment.js +349 -0
  89. package/dist/src/disk.d.ts +17 -12
  90. package/dist/src/disk.js +43 -17
  91. package/dist/src/doctor.d.ts +35 -1
  92. package/dist/src/doctor.js +316 -41
  93. package/dist/src/durable-ticket-lock.d.ts +24 -0
  94. package/dist/src/durable-ticket-lock.js +232 -0
  95. package/dist/src/enroll.d.ts +1 -1
  96. package/dist/src/enroll.js +13 -7
  97. package/dist/src/env.d.ts +10 -1
  98. package/dist/src/env.js +11 -3
  99. package/dist/src/errors.d.ts +8 -8
  100. package/dist/src/errors.js +6 -6
  101. package/dist/src/flush.d.ts +12 -0
  102. package/dist/src/flush.js +37 -0
  103. package/dist/src/heartbeat.d.ts +86 -12
  104. package/dist/src/heartbeat.js +415 -29
  105. package/dist/src/index-background-watchdog.d.ts +1 -0
  106. package/dist/src/index-background-watchdog.js +94 -0
  107. package/dist/src/index-background-work.d.ts +21 -0
  108. package/dist/src/index-background-work.js +25 -0
  109. package/dist/src/index-background.d.ts +64 -0
  110. package/dist/src/index-background.js +394 -0
  111. package/dist/src/index-build-child.d.ts +1 -0
  112. package/dist/src/index-build-child.js +109 -0
  113. package/dist/src/index-manifest.d.ts +52 -0
  114. package/dist/src/index-manifest.js +444 -0
  115. package/dist/src/index-publication-proof.d.ts +84 -0
  116. package/dist/src/index-publication-proof.js +380 -0
  117. package/dist/src/index-publication-state.d.ts +149 -0
  118. package/dist/src/index-publication-state.js +696 -0
  119. package/dist/src/index-publication-verifier.d.ts +89 -0
  120. package/dist/src/index-publication-verifier.js +341 -0
  121. package/dist/src/index-publish.d.ts +62 -0
  122. package/dist/src/index-publish.js +540 -0
  123. package/dist/src/index-scratch-cleanup.d.ts +19 -0
  124. package/dist/src/index-scratch-cleanup.js +166 -0
  125. package/dist/src/index-segment-types.d.ts +132 -0
  126. package/dist/src/index-segment-types.js +21 -0
  127. package/dist/src/index-segments.d.ts +9 -0
  128. package/dist/src/index-segments.js +516 -0
  129. package/dist/src/index-store.d.ts +123 -0
  130. package/dist/src/index-store.js +495 -0
  131. package/dist/src/index-sync.d.ts +91 -15
  132. package/dist/src/index-sync.js +286 -53
  133. package/dist/src/index-upgrade-publication.d.ts +30 -0
  134. package/dist/src/index-upgrade-publication.js +179 -0
  135. package/dist/src/integration-manager.d.ts +32 -0
  136. package/dist/src/integration-manager.js +394 -0
  137. package/dist/src/leakscan.js +1 -1
  138. package/dist/src/local-api.d.ts +56 -15
  139. package/dist/src/local-api.js +4979 -541
  140. package/dist/src/machine-settings.d.ts +51 -0
  141. package/dist/src/machine-settings.js +166 -0
  142. package/dist/src/managed-chunks.d.ts +5 -2
  143. package/dist/src/managed-chunks.js +14 -14
  144. package/dist/src/mcp-install.d.ts +11 -9
  145. package/dist/src/mcp-install.js +73 -28
  146. package/dist/src/mcp-workspace.d.ts +18 -0
  147. package/dist/src/mcp-workspace.js +50 -0
  148. package/dist/src/mcp.js +294 -25
  149. package/dist/src/migrate.js +27 -21
  150. package/dist/src/notify.d.ts +1 -1
  151. package/dist/src/notify.js +5 -5
  152. package/dist/src/offload.d.ts +201 -14
  153. package/dist/src/offload.js +1848 -140
  154. package/dist/src/onboarding.d.ts +8 -1
  155. package/dist/src/onboarding.js +4 -4
  156. package/dist/src/packages.d.ts +2 -2
  157. package/dist/src/packages.js +10 -2
  158. package/dist/src/passkey.d.ts +0 -1
  159. package/dist/src/passkey.js +2 -7
  160. package/dist/src/password-lock.d.ts +2 -2
  161. package/dist/src/password-lock.js +6 -6
  162. package/dist/src/paths.d.ts +2 -0
  163. package/dist/src/paths.js +2 -0
  164. package/dist/src/presence.d.ts +86 -0
  165. package/dist/src/presence.js +240 -0
  166. package/dist/src/progress-deadline.d.ts +21 -0
  167. package/dist/src/progress-deadline.js +91 -0
  168. package/dist/src/project-repair.d.ts +55 -0
  169. package/dist/src/project-repair.js +131 -0
  170. package/dist/src/providers/gcs.d.ts +28 -7
  171. package/dist/src/providers/gcs.js +35 -24
  172. package/dist/src/providers/gdrive.d.ts +71 -8
  173. package/dist/src/providers/gdrive.js +223 -50
  174. package/dist/src/providers/index.d.ts +11 -3
  175. package/dist/src/providers/index.js +30 -10
  176. package/dist/src/providers/s3.d.ts +30 -8
  177. package/dist/src/providers/s3.js +41 -30
  178. package/dist/src/providers/safe-storage-fetch.d.ts +12 -0
  179. package/dist/src/providers/safe-storage-fetch.js +72 -0
  180. package/dist/src/queue.d.ts +136 -19
  181. package/dist/src/queue.js +862 -96
  182. package/dist/src/reclaim-transaction.d.ts +156 -0
  183. package/dist/src/reclaim-transaction.js +1027 -0
  184. package/dist/src/recovery-codes.d.ts +32 -0
  185. package/dist/src/recovery-codes.js +338 -0
  186. package/dist/src/recovery.js +12 -9
  187. package/dist/src/rehydrate.d.ts +25 -22
  188. package/dist/src/rehydrate.js +319 -23
  189. package/dist/src/restore.d.ts +57 -4
  190. package/dist/src/restore.js +272 -36
  191. package/dist/src/resume-stub.d.ts +92 -0
  192. package/dist/src/resume-stub.js +417 -0
  193. package/dist/src/retention.d.ts +98 -7
  194. package/dist/src/retention.js +1066 -62
  195. package/dist/src/rotate.js +3 -3
  196. package/dist/src/search.d.ts +566 -8
  197. package/dist/src/search.js +5897 -290
  198. package/dist/src/secrets.d.ts +51 -7
  199. package/dist/src/secrets.js +316 -24
  200. package/dist/src/service.d.ts +49 -11
  201. package/dist/src/service.js +776 -35
  202. package/dist/src/share.js +3 -3
  203. package/dist/src/shared-spaces.d.ts +98 -0
  204. package/dist/src/shared-spaces.js +214 -0
  205. package/dist/src/source-reader.d.ts +73 -0
  206. package/dist/src/source-reader.js +715 -0
  207. package/dist/src/spool.d.ts +1 -1
  208. package/dist/src/spool.js +1 -1
  209. package/dist/src/start-tui.js +2 -1
  210. package/dist/src/start.js +2 -2
  211. package/dist/src/storage-endpoint.d.ts +21 -0
  212. package/dist/src/storage-endpoint.js +122 -0
  213. package/dist/src/storage-setup.js +12 -12
  214. package/dist/src/storage-targets.d.ts +109 -6
  215. package/dist/src/storage-targets.js +975 -67
  216. package/dist/src/stream-to-cloud.d.ts +5 -1
  217. package/dist/src/stream-to-cloud.js +34 -14
  218. package/dist/src/sync-rules.d.ts +31 -6
  219. package/dist/src/sync-rules.js +153 -14
  220. package/dist/src/team-backfill-scheduling.d.ts +8 -0
  221. package/dist/src/team-backfill-scheduling.js +33 -0
  222. package/dist/src/team-backfill.d.ts +116 -0
  223. package/dist/src/team-backfill.js +1429 -0
  224. package/dist/src/team-index-cache.d.ts +16 -0
  225. package/dist/src/team-index-cache.js +152 -0
  226. package/dist/src/team-offboarding.d.ts +38 -0
  227. package/dist/src/team-offboarding.js +1043 -0
  228. package/dist/src/team-presence.d.ts +127 -0
  229. package/dist/src/team-presence.js +904 -0
  230. package/dist/src/team-publication-policy.d.ts +20 -0
  231. package/dist/src/team-publication-policy.js +140 -0
  232. package/dist/src/team-realtime.d.ts +68 -0
  233. package/dist/src/team-realtime.js +816 -0
  234. package/dist/src/team-source-facts-cache.d.ts +23 -0
  235. package/dist/src/team-source-facts-cache.js +255 -0
  236. package/dist/src/trash.d.ts +1 -1
  237. package/dist/src/trash.js +2 -2
  238. package/dist/src/tui.js +11 -12
  239. package/dist/src/types.d.ts +173 -7
  240. package/dist/src/types.js +20 -0
  241. package/dist/src/ui-server.d.ts +163 -35
  242. package/dist/src/ui-server.js +712 -72
  243. package/dist/src/ui.d.ts +1 -2
  244. package/dist/src/ui.js +1 -2
  245. package/dist/src/upload.d.ts +27 -0
  246. package/dist/src/upload.js +383 -43
  247. package/dist/src/vault.d.ts +226 -30
  248. package/dist/src/vault.js +1776 -192
  249. package/dist/src/watcher.d.ts +7 -1
  250. package/dist/src/watcher.js +198 -55
  251. package/dist/src/worker.d.ts +27 -3
  252. package/dist/src/worker.js +274 -55
  253. package/package.json +33 -12
  254. package/scripts/native-reboot-rehearsal.mjs +90 -0
  255. package/web/app.js +6032 -343
  256. package/web/bootstrap.js +17 -0
  257. package/web/index.html +255 -57
  258. package/web/rail.js +317 -40
  259. package/web/retention.html +2 -2
  260. package/web/rules-view.js +188 -16
  261. package/web/sessions-view.js +485 -62
  262. package/web/sessions.html +2 -2
  263. package/web/setup-api.js +152 -29
  264. package/web/setup-logic.js +68 -9
  265. package/web/setup.html +113 -44
  266. package/web/setup.js +604 -71
  267. package/web/style.css +513 -98
  268. package/dist/packages/vaultline-crypto/src/chunk-access.js +0 -93
  269. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.d.ts +0 -0
  270. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.js +0 -0
  271. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.js +0 -0
  272. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.d.ts +0 -0
  273. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.d.ts +0 -0
  274. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.js +0 -0
package/ARCHITECTURE.md CHANGED
@@ -25,13 +25,19 @@ The client is trusted to handle plaintext. The control plane and object store ar
25
25
  segments hashed under a storage-scoped key, readable identity in a sealed sidecar.
26
26
  - `index-sync`: the sealed content index, pulled and pushed as ciphertext so every
27
27
  machine of the vault searches the whole history.
28
- - `retention`: active-session detection, policy evaluation, grace/trash staging.
29
- - `restore`: integrity checks, atomic output, native adapter mapping; `rehydrate`
30
- restores an archived transcript before an agent's resume reads it.
28
+ - `retention`: active-session detection, policy evaluation, verified/searchable gates,
29
+ and journaled source staging with a durable permanent-purge boundary.
30
+ - `restore`: integrity checks, atomic output, native adapter mapping; reclaimed
31
+ Codex history keeps a lightweight native pointer and full restore is explicit.
32
+ `openFromCloud` is the no-local-record path — a machine reading an archive it
33
+ never sealed — and so must discover the remote layout itself: a chunk folder
34
+ is reassembled from the sealed sidecar's own chunk headers, hash-checked per
35
+ chunk, because the record that would otherwise describe it lives on somebody
36
+ else's laptop.
31
37
  - `local-api`: loopback API shared by CLI/TUI/desktop/MCP.
32
38
 
33
39
  Implemented as: `src/adapters.ts`, `src/watcher.ts`, `src/queue.ts`, `src/worker.ts`,
34
- `packages/vaultline-crypto/`, `src/providers/`, `src/secrets.ts`, `src/upload.ts`,
40
+ `packages/sealkeep-crypto/`, `src/providers/`, `src/secrets.ts`, `src/upload.ts`,
35
41
  `src/chunk-store.ts`, `src/index-sync.ts`, `src/stream-to-cloud.ts`,
36
42
  `src/retention.ts` with `src/activity.ts` and `src/trash.ts`, `src/restore.ts` with
37
43
  `src/rehydrate.ts`, `src/share.ts`, `src/rotate.ts`,
@@ -63,11 +69,41 @@ archive on retry.
63
69
 
64
70
  ## 2.2 Hook boundary
65
71
 
66
- Hook commands hold no secret. They parse the lifecycle payload for a transcript
67
- path (`transcript_path`, `rollout_path`, and camelCase spellings), verify it is
68
- readable, enqueue, and exit. Unknown vendor fields are ignored rather than parsed,
69
- so a schema change cannot break preservation. Encryption happens later in
70
- `sealkeep queue run`, which is the only process that sees the recovery phrase.
72
+ Hook command lines hold no secret. Preservation hooks parse the lifecycle payload
73
+ for a transcript path (`transcript_path`, `rollout_path`, and camelCase spellings),
74
+ verify it is readable, enqueue, and exit. Unknown vendor fields are ignored rather
75
+ than parsed, so a schema change cannot break preservation. Archive encryption
76
+ happens later in `sealkeep queue run`.
77
+
78
+ Automatic context has a deliberately split latency contract:
79
+
80
+ - `hook context` is the synchronous agent-facing lane. It uses the machine's
81
+ normal keystore, atomically claims one prepared context file, authenticates and
82
+ decrypts it locally, and exits. It performs no discovery, hosted request,
83
+ archive refresh, search, publication, or membership reconciliation. Prepared
84
+ context is consumed once and the hook fails open after two seconds.
85
+ - `hook context-sync` is the asynchronous producer. It publishes team progress
86
+ before expensive recall work, reconciles the local realtime inbox, refreshes
87
+ and searches the encrypted index, then seals the bounded result locally for a
88
+ later safe boundary. Provider latency therefore changes freshness, never the
89
+ latency of the current prompt or tool result.
90
+
91
+ Autopilot has a similarly explicit deletion boundary. A fresh installation
92
+ preserves, indexes, and uploads, while `local-settings.json` records
93
+ `reclaimEnabled: false`; neither the generated service unit nor the running
94
+ daemon receives standing reclaim permission. `sealkeep autopilot --reclaim`
95
+ or the confirmed local Settings switch records that permission. Retention
96
+ rules remain a separate eligibility layer: enabling the machine switch repairs
97
+ a non-reclaiming policy to `archive-and-reclaim`, while selecting a
98
+ keep-originals policy revokes the machine switch. Re-running Autopilot during
99
+ an upgrade preserves an existing saved choice and its timing/manual-approval
100
+ policy instead of applying fresh-install defaults.
101
+
102
+ Session resume remains non-blocking: a reclaimed Codex transcript keeps its same
103
+ session id as a tiny valid native pointer. The resumed agent uses MCP search for
104
+ relevant preserved memory. Downloading every historical byte is deliberately an
105
+ explicit `sealkeep recover <archive-id> --native --overwrite backup` action, so a
106
+ resume neither downloads gigabytes nor races an agent's open transcript handle.
71
107
 
72
108
  ## 3. Archive envelope (implemented, format v2)
73
109
 
@@ -90,7 +126,7 @@ time before any chunk is touched. An archive is durable only after the provider
90
126
  the byte count and checksum the client computed; `remote.verifiedAt` records that, and
91
127
  retention reads it.
92
128
 
93
- Full specification, test vectors, and tamper coverage: `packages/vaultline-crypto/`.
129
+ Full specification, test vectors, and tamper coverage: `packages/sealkeep-crypto/`.
94
130
 
95
131
  ## 4. Control-plane API v1
96
132
 
@@ -98,7 +134,7 @@ Authentication is on by default. `SEALKEEP_CONTROL_PLANE_DEV=1` disables device
98
134
  and keeps state in memory for local interface work; `/health` reports
99
135
  `mode: development-interface` so a misconfigured deployment is visible in one request.
100
136
 
101
- All non-enrollment endpoints require an Ed25519 device signature over
137
+ All non-enrollment endpoints require an Ed25519 device signature over the frozen legacy protocol string
102
138
  `vaultline-request:v1\n<METHOD>\n<path>\n<timestamp>\n<nonce>\n<sha256(body)>`, presented in
103
139
  `x-vaultline-device`, `-timestamp`, `-nonce`, and `-signature`. Timestamps outside a
104
140
  five-minute window are refused, and a nonce cache rejects verbatim replays.
@@ -132,6 +168,119 @@ deletes an archive; the response reports how many archives were retained. Revoca
132
168
  retroactive for ciphertext an attacker already holds — the client-side counterpart is
133
169
  `sealkeep recipients remove` followed by `sealkeep rewrap`.
134
170
 
171
+ ### What the plan buys
172
+
173
+ Sealkeep works without an account. The vault, the sealing, the restore, the
174
+ search and your own bucket are all local and always free — that is the product,
175
+ and none of it phones anywhere. What a plan buys is the small hosted layer that
176
+ removes setup nobody wants to do themselves, and it is enforced here rather
177
+ than in the client, because a check the client makes is a check the customer
178
+ can delete.
179
+
180
+ | Capability | Needs | Why it costs |
181
+ | --- | --- | --- |
182
+ | Local vault, archive, restore, search | free | never touches this plane |
183
+ | Your own bucket (S3, R2, B2, GCS) | free | your provider, your bill |
184
+ | Key escrow — a sealed copy of the vault key | any paid tier | we hold and mind a row for you |
185
+ | Machine registry, per-machine delete rights, revocation | any paid tier | identity is coordination, which is what this plane is for |
186
+ | Connect Google Drive | any paid tier | our OAuth app and our server-held client secret |
187
+ | Managed storage (upload, index slot) | `pro` / `team` | our GCS, our bytes, metered against quota |
188
+
189
+ The tiers are `free | pro_lite | pro | team` on `account.plan`. Every gate
190
+ answers **402**, never 400 or 403, so a client can tell "you have not paid for
191
+ this" apart from "you are not allowed to do this" — they need different words
192
+ in front of a person, and the CLI codes the first as `payment_required`.
193
+
194
+ Seats are the other axis, enforced at device registration and counted over
195
+ **active** machines only, so revoking one frees its seat at once: `pro_lite` 3,
196
+ `pro` 10, `team` 100, and `free` cannot register a machine at all. The panel's
197
+ `PACKAGES` list carries the same numbers; they have to be changed together or
198
+ the page starts promising something the plane refuses.
199
+
200
+ `team` is what the B2B features already in the client hang from — recipient
201
+ keys (`sealkeep recipients` + `rewrap`), project-scoped membership
202
+ (`--project`, `space members`), owner-key delete authority, and corporate
203
+ bucket routing. None of that was new work; what was missing was a tier to sell
204
+ it as and a limit that meant anything.
205
+
206
+ A warning from writing these guards: **error strings are expression-evaluated**,
207
+ so a bare word that matches an assigned variable name is rewritten. "Every
208
+ machine seat on this plan is in use" shipped to a customer as "Every
209
+ vars.machine seat…" because a later step assigns `machine`. Re-read every
210
+ refusal against a live account before believing it.
211
+
212
+ **A lapsed subscription stops writes at once and reads after 30 days.** The
213
+ grace window runs from `subscription.current_period_end` (falling back to
214
+ `updated_at`), and the download lease reports `reading_on_grace` and
215
+ `grace_until` so a client can warn before the door shuts. The asymmetry is
216
+ deliberate: the bytes are the customer's, and a billing failure should never be
217
+ the reason they cannot get their own history out. Archives on their own bucket
218
+ or their own Drive are untouched by any of this — they were never ours to hold.
219
+
220
+ Escrow READS are never gated. Refusing someone the sealed copy of their own
221
+ vault key because a card expired could lock them out of everything they own.
222
+
223
+ ### `/v1/cloud/security/*` — watching without reading
224
+
225
+ The plane holds ciphertext. It could not scan a transcript for a leaked key if
226
+ it wanted to, and it should not want to. So the scan runs on the developer's
227
+ own machine, where the plaintext briefly exists, and only the verdict travels:
228
+ the rule that matched, how many times, which machine, which archive.
229
+
230
+ Never the matched text. Never the surrounding line. Not even `maskSecret`'s
231
+ preview — that is masked well enough for a terminal, not well enough for a
232
+ database that keeps it forever. And never the project name, which by design
233
+ never reaches this plane at all. `test/security-findings.test.ts` pins the wire
234
+ shape as an allow-list, so a new field is a deliberate decision rather than an
235
+ accident.
236
+
237
+ `POST /security/findings` records one row per rule, not per match: a session
238
+ that pasted the same key forty times is one problem, and forty rows would bury
239
+ the other findings under it. `GET /security/findings` is the company's feed.
240
+ `GET /security/policy` hands every machine the company's own patterns, plus
241
+ `block_on_high`, which is what turns detection into prevention — a machine that
242
+ pulls it refuses to seal a session carrying a high-severity match rather than
243
+ reporting it afterwards.
244
+
245
+ Reporting is best-effort by construction. An account below `team` answers 402
246
+ and the client stops asking; a network failure loses a report. The one thing it
247
+ must never do is cost anyone a seal.
248
+
249
+ ### Seats, and why they are not storage
250
+
251
+ `account.seats` is the purchased machine allowance and beats the tier default.
252
+ It exists because storage is the wrong axis for a company: a team that keeps
253
+ everything in its own bucket consumes none of ours and would otherwise pay for
254
+ a storage tier it never touches. Seats and `quota_bytes` are priced apart, so
255
+ bring-your-own-bucket costs seats alone.
256
+
257
+ ### Self-hosting
258
+
259
+ `SEALKEEP_CLOUD_URL` points a machine at any compatible plane, and every path
260
+ that reaches this one honours it — `cloud.ts`, `enroll.ts`, `local-api.ts`,
261
+ `storage-targets.ts`. The only hard-coded address is `DEFAULT_CLOUD_URL`. The
262
+ test suite runs a full seal → chunk upload → download → restore against a stub
263
+ plane on `127.0.0.1` via that variable, so this is a tested path rather than an
264
+ aspiration. What a self-hosting customer needs from us is the plane itself and
265
+ this contract, not a client change.
266
+
267
+ ### `DELETE /v1/cloud/archives/:vault_key`
268
+
269
+ Destroying is authorised separately from reading, because the bearer token
270
+ names the account and not the machine: every laptop paired to a company account
271
+ presents an identical one. The body carries either a **machine key** (issued
272
+ once at device registration, only its bcrypt is stored) or an **owner key**
273
+ (minted by a completed web sign-in, which a CLI token cannot perform). An
274
+ archive records `sealed_by_machine` at upload completion; a machine may delete
275
+ what it sealed, an owner key may delete anything, and everything else is
276
+ refused. Allowed and refused attempts both write an audit row. Archives
277
+ predating this carry no stamp and remain account-wide deletable — attribution
278
+ starts at the next upload.
279
+
280
+ Credentials travel in the request body rather than a header because this
281
+ control plane does not surface request headers to endpoint logic; that was
282
+ measured, not assumed.
283
+
135
284
  ### `GET /v1/audit`
136
285
 
137
286
  Account-scoped audit events: action, outcome, identifiers, and counts. No content.
@@ -167,7 +316,7 @@ command. Bodies are capped at 1 MB, validated with strict schemas, and rejected
167
316
 
168
317
  ## 5. Local MCP contract
169
318
 
170
- MCP tools call local APIs, not provider SDKs directly. `vaultline_create_upload` returns a lease object; it must never receive a cloud secret. Search defaults to metadata. Recover and archive require explicit local authorization/recovery secret.
319
+ MCP tools call local APIs, not provider SDKs directly. `sealkeep_create_upload` returns a lease object; it must never receive a cloud secret. Search defaults to metadata. Recover and archive require explicit local authorization/recovery secret.
171
320
 
172
321
  ## 6. Failure behavior
173
322
 
@@ -184,8 +333,8 @@ Implemented and tested end to end: capture (hooks, watcher, durable queue), encr
184
333
  (format v2 with vectors and a tamper suite), provider clients (SigV4 verified against AWS's
185
334
  published example; multipart, GCS resumable, and the plain object verbs exercised against
186
335
  in-process emulators), verified upload, the chunk-folder remote layout with hashed naming
187
- and sealed identity, retention with trash staging, restore including native adapter paths
188
- and rehydrate-on-resume, the sealed index sync, the loopback API behind the dashboard and
336
+ and sealed identity, retention with crash-safe staging and permanent source reclaim, restore including native adapter paths
337
+ and the lightweight resume pointer, the sealed index sync, the loopback API behind the dashboard and
189
338
  TUI, the MCP server and its installer, the control plane with device auth and audit,
190
339
  search, and the local audit log.
191
340
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,227 @@
3
3
  Notable changes, by published version. Sealkeep is pre-1.0: minor versions
4
4
  may change behavior, and say so here when they do.
5
5
 
6
+ ## 0.11.0 — 2026-09-16 — automatic memory that works, and an append-only index
7
+
8
+ **Automatic recall now works on a real, long-lived vault.** The hook lane
9
+ (`sealkeep hook context-sync` → daemon → `sealkeep hook context`) silently did
10
+ nothing on a vault with months of history; every cause below was found by
11
+ running it, not reading it.
12
+
13
+ - Test daemons no longer re-target the machine's real agent hooks at a
14
+ temporary vault; the installer repairs hooks aimed at a vanished vault, and
15
+ `sealkeep doctor` fails, naming the path, when hooks route to a different
16
+ live vault.
17
+ - The recall child is no longer CPU-paused behind the hours-long index
18
+ catch-up; each recall bounds its hosted index refresh, its start-of-session
19
+ team steps and its presence publish/catch-up to seconds, answering from the
20
+ local index when the plane is slow. The presence upload was a silent three-
21
+ to-six-second tail that tripped the stall deadline after the scan had
22
+ finished, so the pass was killed at the finish line and retried.
23
+ - Nothing is injected when nothing was recalled. Personal vaults recall across
24
+ untagged sessions (hand-archived, or sealed before project tags existed)
25
+ without ever crossing into a different stable project key; `sealkeep archive
26
+ <file>` tags the agent and project itself.
27
+ - Recall picks up to three specific terms from the prompt (never the project
28
+ name), prefers a session's later excerpts, and stops after the second
29
+ productive scan. "Recent preserved sessions" lists each session once, by its
30
+ newest snapshot: a live session is sealed every few minutes, and its
31
+ snapshots used to fill all five lines and hide every other session.
32
+ - A session's end drops its queued recall work — including what a pass still
33
+ running for it writes afterwards; within a priority tier the newest request
34
+ is served first, so a finished session never delays the one being typed in.
35
+ Day-old prepared context, state and locks are swept, and the daemon's first
36
+ heartbeat removes replace-temporaries left by unclean stops.
37
+ - The recall child's no-progress deadline is 20 s (was 8 s): a healthy pass on
38
+ a large vault reports progress 4–6 s apart under ordinary load, and every
39
+ budgeted hosted wait now keeps reporting progress while it lasts. The
40
+ two-minute absolute ceiling is unchanged.
41
+ - The service heartbeat records the recall lane (`context`: state, consecutive
42
+ failures, last error as a stable word), and `sealkeep doctor` reports
43
+ `automatic-recall` — a warning after three failed passes in a row, otherwise
44
+ when memory was last prepared. Hooks fail open, so this is the only place a
45
+ dying lane is distinguishable from one with nothing to recall.
46
+ - A memory hook that cannot unlock the vault still answers the agent's empty
47
+ object, but now leaves a marker the next successful hook clears, and doctor
48
+ fails `automatic-recall` naming the agent, the time and the secret backend
49
+ the hook ran with. On a real host the hooks were pinned to a secret backend
50
+ that no longer held the phrase: sealing and uploading kept working while
51
+ indexing, recall and hook capture were silently off.
52
+ - `sealkeep_recover` is dry-run by default, as its context pack already said;
53
+ `sealkeep recover --dry-run` matches.
54
+ - A seal now keeps the tokens it extracted from the plaintext as a sealed
55
+ sidecar beside the record (`<archive-id>.sktokens`, kilobytes) before it
56
+ merges them into the index, and the index catch-up indexes such an archive
57
+ from that file alone — nothing is decrypted or downloaded again. On a real
58
+ vault the merge had failed silently for 2,300 seals in one week, discarding
59
+ their tokens and re-queuing 1.85 TB of history for a full decrypt that never
60
+ completed. A merge or sidecar that fails is now named (`SealkeepIndexDeferred`
61
+ warning; `index.merge` / `index.tokens` audit entries).
62
+ - **A term lookup no longer reads the whole index.** The index is sealed in
63
+ independent chunks (`zstd-chunk`) and each checkpoint writes a sealed term
64
+ directory beside it (`content-index.directory.skindex`: the byte offset of
65
+ every 64th token row and the archive/metadata regions). A query
66
+ binary-searches the directory, opens only the chunks holding its window and
67
+ parses a few hundred rows — the old read decrypted and inflated 100 MB and
68
+ parsed 1.5 million rows for every term, 8–12 s each on a real vault. An
69
+ index without a usable directory (older, or pulled from another machine)
70
+ still takes the full read.
71
+ - **The index is append-only.** `sealkeep index migrate` moves a vault's
72
+ index onto sealed, immutable segments (`index/segments/`: one sealed body,
73
+ its envelope and a sealed term directory per segment, listed in a plaintext
74
+ manifest that holds only ids and counts). A sealed memory never changes, so
75
+ its index rows are written once: a seal appends a one-archive segment
76
+ instead of re-reading, rewriting and re-uploading the whole index; the
77
+ catch-up writes each checkpoint's batch as a segment and never holds more
78
+ than one batch, so the background child no longer dies on a large vault
79
+ (2 million terms exceeded its 1 GB heap on every attempt); a removed archive
80
+ becomes a tombstone that every lookup and the next compaction honour.
81
+ Compaction streams small segments into one bigger immutable segment — the
82
+ small-segment rule at seal time, the full policy in the build lane. A
83
+ lookup opens every segment's directory and reads only the blocks holding
84
+ its terms, then merges: 0.3–0.8 s per term on a 2-million-term vault, in a
85
+ process that stays under 350 MB. `index drop` keeps a migrated vault on
86
+ segments; a changed extraction policy resets the segments and re-indexes
87
+ instead of looping the daemon on "coverage incomplete"; `index status`
88
+ answers from the manifest. The single blob is still produced for the cloud
89
+ by a foreground build (the background child publishes only when the merged
90
+ index fits 400,000 tokens), and another machine's newer cloud index is not
91
+ yet folded into segments — both follow when segments themselves are
92
+ published; until then a segmented vault skips the pull of that blob, which
93
+ cost every recall pass the 2 s it was budgeted and a plain search 30 s on a
94
+ slow plane. Opening a segment unwraps its key once per process (two scrypt
95
+ derivations, ~70 ms each, were paid again for every decrypted window): a
96
+ term lookup is 5–40 ms, a full merge across segments under 200 ms, and
97
+ prompt → prepared memory on the 4,000-archive vault is 3.0–3.5 s.
98
+ - **A local seal indexes itself.** Only the cloud seal path collected a
99
+ session's tokens as it sealed; a session sealed locally (no cloud, a cloud
100
+ that is unreachable, `sealkeep archive`) waited for the background catch-up,
101
+ which on a real vault sits behind hours of multi-gigabyte snapshots — the
102
+ session a person had just finished was the last thing that became
103
+ searchable. The daemon, the CLI and the MCP server now index a full seal as
104
+ part of the seal (chunk-compressed, up to the whale threshold; a delta is
105
+ still indexed as one chain by the catch-up), on segments always and on a
106
+ blob index while it is small and no catch-up is building it (the blob has
107
+ one writer at a time) — a large blob is merged whole in-process, and that
108
+ is the crash the segments exist to avoid, so a large vault gets seal-time
109
+ indexing by migrating. As on the cloud path, a failed entry is
110
+ named (`SealkeepIndexDeferred`, audit `index.merge`), never a failed seal.
111
+ - **Segments travel between machines.** On a store that can hold objects
112
+ (bring-your-own storage) a machine uploads each of its immutable segments
113
+ once — three objects per segment, a per-machine head, a heads map for
114
+ discovery — and a peer downloads only the segments it has not seen,
115
+ registering them as remote: never compacted with its own, replaced when
116
+ the source publishes a newer set, the source's tombstones honoured. The
117
+ managed plane holds only the index's meta and blob objects, so a
118
+ segmented vault there keeps publishing the blob merged from its segments
119
+ (a foreground build always, the background child while the merge fits
120
+ 400,000 tokens) and converts a peer's newer blob into one remote segment,
121
+ skipping its own publication and any blob over 64 MiB. The daemon runs a
122
+ publish pass whenever the manifest has moved since the last publication,
123
+ so a seal reaches the other machines without waiting for a catch-up.
124
+ - **A transcript is indexed once.** A full re-seal of a file that replaces an
125
+ earlier snapshot (`supersedes`) is now the only snapshot of that file the
126
+ index holds: older snapshots are skipped by the catch-up, purged from a
127
+ blob index and tombstoned on segments, and `index status` no longer counts
128
+ them as missing. On a real vault the catch-up backlog was 416 snapshots of
129
+ a handful of multi-gigabyte Codex rollouts — one June rollout of 2.7 GB had
130
+ been sealed 88 times, and every copy was queued for a full decrypt. A
131
+ delta seal (the bytes appended since the last seal) now indexes those bytes
132
+ inline, under its own id and chunks, instead of waiting for the catch-up to
133
+ decrypt the whole chain again, each part answering for its own bytes.
134
+ - Migrating a vault whose index carried another machine's sessions (pulled
135
+ from the account index) no longer loses them: the first build after the
136
+ migration tombstoned every one of them as "indexed here but no longer here"
137
+ — on the rehearsal's dev vault, all eleven of a teammate's sessions. A
138
+ migrated segment now records which rows are this machine's and which came
139
+ from elsewhere; only the former can ever be pruned, and `index status`
140
+ counts both.
141
+ - Interrupted builds no longer leave the plaintext index behind. A killed
142
+ catch-up (the background child running out of memory, a reboot) left its
143
+ streaming checkpoint's staging file — every token of every session, in the
144
+ clear — under `index/` forever, because the sweep only knew sealed temps; a
145
+ real vault held 1.9 GB of them. Staging files older than an hour are swept
146
+ at the start of every build, under `index/` and `index/segments/` alike.
147
+ - Automatic recall runs its terms concurrently, quotes two sessions per term
148
+ (`snippetLimit`) and takes sessions whose sealed bytes are on this disk
149
+ first (`quoteLocalFirst`) — a reclaimed session's quote is a ranged fetch
150
+ from the cloud, seconds on a slow plane. The top-ranked cloud-only session
151
+ still keeps its quote (one per term): a teammate's session is never on this
152
+ disk, and the rehearsal showed the person's own sessions filling every slot
153
+ while the colleague's decision ranked first and went unquoted.
154
+ - A preserved Claude Code session's one line ("Recent preserved sessions", a
155
+ teammate's finished note) is its last assistant message. The scrape used to
156
+ take the last string in the file, which in a Claude transcript is the
157
+ harness's own reminder — so a session read as `<system-reminder>
158
+ <total_tokens>…`. Harness scaffolding is skipped in the fallback too, an
159
+ index pass re-derives a scaffold line while the source is still on disk,
160
+ and a quote never comes from the harness or from memory Sealkeep itself
161
+ injected on an earlier turn (a transcript carries both, and both matched —
162
+ a matching line with nothing else to show is dropped rather than quoted
163
+ raw). When two copies of the index are merged, a real one-liner wins over
164
+ a scaffold one from either side. A budgeted hosted index refresh is
165
+ attempted once a minute per process instead of once per lookup, and search
166
+ ranking no longer scans the record list inside its comparator. Prompt →
167
+ prepared memory on a 4,000-archive vault: 15 s → 2.5–4 s.
168
+ - The index build interns posting tags (one string per archive chunk instead
169
+ of one per posting — 19 million on a real vault) and no longer indexes
170
+ numbers of four or more digits or digit-bearing tokens of sixteen or more
171
+ characters (a quarter of a real vault's vocabulary: line numbers, byte
172
+ counts, timestamps, base64 slivers). Ids such as `p04946`, `s3`, `sha256`,
173
+ `429` stay searchable.
174
+ - The index catch-up schedules archives over 256 MB after every ordinary
175
+ session (newest-first within each group). Eight ~2.6 GB snapshots at the
176
+ head of a real vault's queue had held the lane for a week: nothing could
177
+ checkpoint before the first finished, and under the shared CPU budget and
178
+ daily restarts it never did. A checkpoint that fails to save is now named
179
+ (`SealkeepIndexCheckpoint` warning) instead of silently making "safe to
180
+ stop; it resumes here" untrue.
181
+
182
+ - Contributor removal preserves authenticated shared history in durable owner
183
+ custody before changing membership, then republishes it for the surviving
184
+ roster. Interrupted handoffs resume without needing the contributor's machine.
185
+ - Invitation acceptance checks a preserved history snapshot atomically. Writes
186
+ made while an invitation is pending require a refreshed snapshot; accepted
187
+ members receive preserved history through the owner's background handoff.
188
+ - Team snippets use the authenticated project index, validate archive facts and
189
+ membership, and enforce bounded streaming reads and decompression. Personal
190
+ index records cannot override the authorization facts used for a shared read.
191
+ - Concurrent backfill staging uses unique temporary files. Archive transfer
192
+ completion is reported only after its final journal state is durable.
193
+ - A secret-free indexing watchdog now resumes and terminates its exact worker
194
+ if the owning service dies while that worker is CPU-paused. Work starts only
195
+ after the watchdog confirms ownership; watchdog failure stops the worker.
196
+ - Concurrent scratch-space claims retry if the competing owner released its
197
+ lease during collision handling. Team realtime shutdown now drains pending
198
+ refreshes and writes instead of reconnecting or writing after close returns.
199
+ - Enterprise deployment acceptance remains pending. The verification record in
200
+ `docs/enterprise-verification-2026-09-05.md` distinguishes hosted tests from
201
+ unverified isolated-runtime promises and records explicit operating limits.
202
+
203
+ ## 0.10.0 — 2026-09-04
204
+
205
+ **Archived Codex sessions stay resumable without keeping their bulky local
206
+ transcripts.** Safe disk reclaim leaves a tiny native rollout at the original
207
+ path, so `codex resume <session-id>` still opens the same thread. The pointer
208
+ tells Codex to use the local Sealkeep MCP, and recall searches every preserved
209
+ snapshot carrying that exact Codex session UUID—not only the most recent
210
+ archive. Questions whose facts span several resume/reclaim cycles are ranked
211
+ inside that session boundary and never widened to unrelated threads.
212
+
213
+ - Reclaim now fails closed unless the encrypted archive and the current
214
+ immutable managed search index are both verified. On a repeated resume it
215
+ also requires a current positive publication receipt and searchable postings
216
+ for every earlier reclaimed continuation, then rechecks all receipts and the
217
+ exact index head immediately before unlinking the source.
218
+ - Historical indexing and context delivery run outside the interactive hook
219
+ path. Background work is serial and cooperative, targets at most 30% CPU,
220
+ shares one 8 Mbit/s vault-wide transfer ceiling, and runs in children with
221
+ explicit heap/RSS, cancellation, and progress-deadline limits.
222
+ - Existing encrypted v3 indexes upgrade through the same managed CAS protocol
223
+ in the bounded background child. A successful publication avoids a full
224
+ rebuild; interrupted publication retains a durable retry marker, while an
225
+ incompatible or divergent index follows the authenticated rebuild path.
226
+
6
227
  ## 0.9.0 — 2026-08-20
7
228
 
8
229
  **Share a project, not the vault.** Until now a registered key opened everything
@@ -48,7 +269,8 @@ that no longer needs a spacious disk.
48
269
  a failed queue job used a stylesheet class that did not exist, five notes
49
270
  used an undefined token, and the retention view typed over edits in progress
50
271
  and could then save the policy you thought you had changed.
51
- - `archiveFile` accepts a chunk size (and honours `SEALKEEP_CHUNK_BYTES`).
272
+ - Archive helpers accept an explicit typed test chunk size; production has no
273
+ undocumented environment override.
52
274
  Proving a multi-chunk archive used to mean writing 24 MB; it now takes 120 KB,
53
275
  which is why the suite stopped exhausting disks.
54
276
  - New: `npm run browser:audit` and `npm run browser:panel` — real Chrome,
package/CONTROL_PLANE.md CHANGED
@@ -7,7 +7,7 @@ The control plane stores device identities, encrypted manifests, and provider co
7
7
  ```sh
8
8
  npm run control-plane # authenticated, durable
9
9
  SEALKEEP_CONTROL_PLANE_DEV=1 npm run control-plane # unauthenticated, in memory
10
- docker build -t vaultline-control-plane . && docker run -p 8787:8787 -v vaultline-data:/data vaultline-control-plane
10
+ docker build -t sealkeep-control-plane . && docker run -p 8787:8787 -v sealkeep-data:/data sealkeep-control-plane
11
11
  ```
12
12
 
13
13
  Endpoints:
@@ -44,7 +44,7 @@ deployed, stop it.
44
44
  ```json
45
45
  {
46
46
  "provider": "s3 | r2 | gcs | vaultline",
47
- "bucket": "vaultline-archives",
47
+ "bucket": "sealkeep-archives",
48
48
  "region": "us-east-1",
49
49
  "prefix": "v1/accounts/<account-id>",
50
50
  "credentialMode": "server-managed | byo-role | byo-service-account"
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Vaultline Software License
1
+ Sealkeep Software License
2
2
 
3
3
  Copyright (c) Spala.
4
4
  All rights reserved.