deepspace 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,251 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **Deploy no longer reads `.dev.vars`. The secret store is the only source.**
8
+
9
+ Deploy used to compare hand-edited `.dev.vars` keys against the store and
10
+ refuse (`secrets_not_uploaded`) or warn when they differed — a guard that
11
+ inferred intent from a local file. It also broke its own escape hatch: the
12
+ refusal pointed at `secrets upload .dev.vars` while that same deploy had
13
+ already written the SDK-managed block into that file, whose keys the upload
14
+ then rejected, with no code and no action to recover from. An agent that hit
15
+ it had no way forward.
16
+
17
+ `.dev.vars` keeps its real job — deploy WRITES it so `deepspace dev` sees the
18
+ same values — and is never read back to decide anything.
19
+
20
+ **Removed:** the `--allow-missing-secrets` flag and the `secrets_not_uploaded`
21
+ refusal code, which only existed to escape that guard.
22
+
23
+ Deploy now distinguishes a missing config from an explicitly created empty
24
+ config. A missing config regenerates the local cache without app secrets and
25
+ refuses before build, Git push, or upload, with an executable
26
+ `secrets configs create` action. An existing empty config is explicit
27
+ delete-all intent. The deploy server independently preserves live bindings
28
+ when an older client omits secret authority, making the rollout safe in both
29
+ directions.
30
+
31
+ Authoritative deletion is part of release finalization, not a best-effort log.
32
+ The server retries Cloudflare reconciliation three times and retains the
33
+ per-app release fence on failure. Replaying the exact deploy retries deletion
34
+ without issuing a second Worker activation; a release is recorded only after
35
+ stale bindings, including `ALLOW_DEBUG_ROUTES`, are gone.
36
+
37
+ If the original CLI process exits after that partial result, a fresh deploy key
38
+ can resume only when the complete Worker, secret, attribution, and source
39
+ lineage hash matches the live operation. The server first proves that exact
40
+ operation is live at Cloudflare; different input stays fenced. Malformed
41
+ successful Cloudflare secret-list envelopes also fail closed.
42
+
43
+ The per-app release transaction is now reserved before route registration,
44
+ binding auto-provisioning, or rate-limit allocation. Losing concurrent deploys
45
+ therefore return without renaming the production app or creating provider
46
+ resources. An incomplete reservation cannot activate; an exact retry may
47
+ atomically take ownership and finish its resolved rollback metadata. Durable
48
+ key aliases keep displaced processes from reclaiming the operation and replay
49
+ the successor after finalization; a safe pre-activation abort removes the
50
+ aliases with the fence. A compare-and-set transition grants exactly one request
51
+ permission to issue the Worker PUT. That winner is bound to a request-scoped
52
+ claim nonce: it can clear its own committed-but-lost claim response before the
53
+ PUT begins, while an uncertain concurrent request cannot clear the winner's
54
+ activation fence.
55
+
56
+ - **`.dev.vars` is now fully generated. Hand edits do not survive.**
57
+
58
+ The file used to be three zones — an SDK section, a hand-edited section
59
+ preserved across runs, and a store cache — which required a divider grammar, a
60
+ dotenv parser to read the file back, and reconciliation between the two
61
+ sources. That made `.dev.vars` a second source of truth, and historically a
62
+ de-facto deploy input (docs/proposals/secrets-source-of-truth.md).
63
+
64
+ Now it is a materialization: rewritten whole on every `dev`, `test`, `deploy`,
65
+ and `secrets pull` run from platform values plus the app's secret store, with
66
+ a header saying so. Nothing reads it back.
67
+
68
+ **If you hand-edited `.dev.vars`, put required values in the store before
69
+ updating** (`deepspace secrets set KEY=value`). The next dev/test/pull run
70
+ overwrites the file. There is no legacy import or compatibility path; beta
71
+ consumers must update their configuration.
72
+
73
+ All Wrangler environments now share the one generated `.dev.vars`. Missing
74
+ configs materialize as empty, so a successful refresh cannot leave stale local
75
+ values behind.
76
+
77
+ Also removed: `secrets upload` no longer strips a generated block (there is no
78
+ block to strip — uploading the generated file is not a flow; use
79
+ `secrets download` for backups).
80
+
81
+ ### Patch Changes
82
+
83
+ - Remove stale lint suppressions from the Messaging and Documents feature
84
+ sources so an app containing every public feature builds without unused-rule
85
+ warnings.
86
+ - Support the maintained Node 22 (22.15+), 24, and 26 release lines. Reject
87
+ end-of-life odd-numbered releases explicitly instead of allowing installs that
88
+ can fail inside their frozen npm dependency resolver.
89
+
90
+ Because npm treats `engines` as a warning by default, `create-deepspace` also
91
+ checks the runtime before prompts, file copies, identity minting, or Git
92
+ initialization. Help and version remain available for diagnosis.
93
+
94
+ - `deepspace pull` no longer traps an agent when local work is simply unpushed.
95
+
96
+ Local-ahead — the ordinary state after `commit` — was classified as divergence.
97
+ `pull` exited 2 with `actionRequired: true` and handed back
98
+ `git merge refs/remotes/space/<branch>`, which answers "Already up to date" and
99
+ changes nothing. An agent honouring the exit-2 contract re-ran `pull` forever;
100
+ only `push` clears the state, and `push` was not the pinned action.
101
+
102
+ It is now `status: "local_ahead"` on a successful (exit 0) pull. Human output
103
+ names `deepspace push`, and JSON carries that same targeted push as its
104
+ executable success action. `deepspace status` has always classified this
105
+ correctly — the two now agree.
106
+
107
+ - Typo'd flags are now refused instead of silently ignored.
108
+
109
+ citty hardcodes its parser to `strict: false`, so an unknown flag was never
110
+ rejected — it was dropped and the command ran with the caller's intent
111
+ discarded. `deepspace releases --limitt 1` swallowed the flag _and_ its value
112
+ and returned every release with exit 0; `--jsonn` printed human prose to stdout
113
+ while the caller waited for JSON. Unknown subcommands and bad flag _values_
114
+ were already rejected; only flag names went unchecked.
115
+
116
+ One check at the shared command boundary now rejects them with
117
+ `code: "unknown_option"` before any side effect, naming the options that verb
118
+ accepts. Aliases, hyphenated names, and `--no-<flag>` are unaffected.
119
+
120
+ - Collaborative apps no longer show stale data as live when the connection dies
121
+ quietly.
122
+
123
+ A peer that stops answering without closing leaves the socket ESTABLISHED — no
124
+ packets are lost, so nothing retransmits, so no close event ever arrives.
125
+ Measured against a peer frozen mid-conversation, an unprobed connection stayed
126
+ open indefinitely. That is a hung Durable Object, a broken relay, or a
127
+ blackholed path; the browser's `offline` event fires for none of them, because
128
+ the machine's own network is fine.
129
+
130
+ Everything downstream already handled this correctly and was simply never
131
+ called: `useRecordContext().status` flips to `'disconnected'`, every live query
132
+ resets to loading while keeping its records on screen, and the socket
133
+ reconnects with backoff. The client now probes a connection once it has gone
134
+ quiet and closes it after 45s of silence, so those run when they should.
135
+
136
+ The probe costs nothing on the server: `BaseRoom` already registers a
137
+ `WebSocketRequestResponsePair('ping','pong')`, so the Cloudflare runtime
138
+ answers it while the Durable Object stays hibernated. An app already receiving
139
+ updates is never probed. Liveness is probe-based: a quiet socket sends a ping
140
+ after 15 seconds and closes only when that outstanding probe receives no
141
+ inbound answer for a further 30 seconds. A backgrounded timer's first resumed
142
+ callback therefore probes instead of falsely disconnecting a healthy socket.
143
+ If suspension occurs after a ping was sent, the first delayed callback also
144
+ discards that pre-suspension judgment and sends a fresh probe before applying
145
+ the normal deadline.
146
+
147
+ ## 0.17.0
148
+
149
+ ### Minor Changes
150
+
151
+ - **`deepspace deploy --claim-released` lets a platform admin take a name still inside its 30-day cooldown.** Undeploying releases a hostname into a hold reserved for its previous owner, and `released_by` records the app's **owner** — not whoever ran the undeploy. So an admin taking down an abandoned or abusive app could not then put anything on that name for a month: the reservation belongs to the absent owner, and the admin's own app matches neither the releasing-owner nor the releasing-app leg that normally allows a reclaim. The name was stranded by the mechanism meant to protect it.
152
+
153
+ The flag is explicit rather than an implicit admin bypass. The override is absolute — it beats a release made seconds ago and permanently discards the reservation rather than pausing it — so a routine admin deploy must not be able to seize a held name by accident. This is the same reasoning that makes a rename require `--rename`. Non-admin accounts are refused with `admin_required`, and because the tier lookup fails closed, a billing outage refuses rather than grants.
154
+
155
+ Two records are written, because neither side knows the whole story: the deploy worker logs the acting admin (it cannot name the displaced owner, since only released rows carry `released_by` and no by-host lookup returns them), and the registry logs the displaced owner and the app that lost the name at the moment the row is replaced.
156
+
157
+ - **Removed: GameRoom, document mode, and the identity-migration wire.** These
158
+ landed in #240 and #241 without a changeset, so this declares them rather than
159
+ letting a release drop public exports silently.
160
+
161
+ Gone from the public surface:
162
+ - `useGameRoom`, and the `GameRoom` durable object with `GameRoomConfig`,
163
+ `GamePlayer`, `UseGameRoomResult`, `Player`, and `GameInput`. No in-repo app
164
+ used it, and it failed the authorization, hibernation, and scheduling
165
+ invariants every other room holds. Use `RecordRoom` for shared state.
166
+ - `CANONICAL_APP_IDENTITY_MIGRATION_ID` and the `MSG.GAME_*` protocol
167
+ constants, which had no producer or consumer once the room went.
168
+ - The RecordRoom document-mode migrations, verified unused across every repo
169
+ in the org before deletion.
170
+
171
+ An app that imported any of these will fail to build. Nothing in the templates
172
+ or the feature catalog referenced them.
173
+
174
+ ### Patch Changes
175
+
176
+ - Fix a set of version-control lifecycle and honesty defects found by
177
+ black-box agent live-testing of multi-checkout collaboration — cases where
178
+ a verb prescribed recovery that deterministically failed, reported work it
179
+ did not do, or handed out a command that could not run where it pointed.
180
+ - **Recovery actions execute verbatim.** Every `action.argv` that re-invokes
181
+ this CLI is pinned to the running interpreter and entry (resolved through
182
+ the `node_modules/.bin` symlink) instead of the bare word `deepspace`,
183
+ which is not on PATH in a linked worktree, a bare clone, or an `npx`
184
+ invocation. One door (`executableAction`) applies it to refusal actions,
185
+ success-path actions, the unknown-command suggestion, and `deploy`'s own
186
+ exit envelope; consumers must not assume `argv[0] === 'deepspace'`.
187
+ - **A checkout can follow its own advice.** `deepspace clone`, `workspace
188
+ new`, `workspace attach`, `workspace sync`, and `workspace land` fill
189
+ whichever half of the checkout's repo-local git identity is missing from
190
+ the session token — previously the `git pull` a divergence refusal handed
191
+ back (and any first commit) died on `unable to auto-detect email address`
192
+ in a container with no global git config, or with a half-configured one.
193
+ The divergence recovery also pins `git pull --no-rebase` (a fresh clone
194
+ has no reconcile config) and exits 2 from workspace publishes exactly as
195
+ it does from `push`.
196
+ - **A second checkout of a landed workspace is no longer a dead end.**
197
+ `workspace drop` cleans up the stale local worktree and branch of a
198
+ workspace another clone landed or dropped — proving publication against a
199
+ freshly fetched trunk tip when the landed ref is gone — and reports
200
+ whether the remote drop actually happened (`json.remoteDropped`) instead
201
+ of claiming a fresh drop on a replay. `workspace status` states the right
202
+ fact per state (landed/dropped → drop cleans up; behind → fast-forward;
203
+ diverged → integrate first) instead of prescribing a `workspace sync` that
204
+ would refuse, and `--json` gains `syncRelation` so machine callers see the
205
+ same distinction. `workspace land` and `workspace sync` on a finished
206
+ workspace report `workspace_not_active` (with the `workspace drop` cleanup
207
+ action) ahead of a checkout mismatch whose attach advice could not
208
+ succeed; drop's own unsynced refusal stops prescribing the publish path
209
+ once the workspace is finished — nothing can publish those commits, and
210
+ the message says so. The server returns `workspace_not_active` (over the
211
+ generic `conflict`) for sync/land/drop against a finished workspace — a
212
+ worker-side change that reaches existing CLIs on deploy; no released CLI
213
+ branches on the old slug, and this CLI tolerates both.
214
+ - **`workspace attach` is idempotent.** Attaching an already-attached
215
+ workspace points at the existing worktree instead of refusing
216
+ `branch_exists` — reporting the LOCAL tip and, when it differs from the
217
+ published tip, the recovery that relation actually admits (`workspace
218
+ sync` when ahead; `git pull --no-rebase` first when behind or diverged,
219
+ since sync would refuse) — and re-materializes a worktree that `git
220
+ worktree remove` or a failed cleanup deleted.
221
+ - **Land refusals carry their resume action, and a recorded merge is never
222
+ reported as a bare failure.** `merge_conflict`, `conflict_markers` (now
223
+ naming the offending files), `validation_failed`, and
224
+ `validation_mutated_tree` carry the exact re-run action; when the trunk
225
+ push succeeded and only recording the land failed, `land_unrecorded`
226
+ states that the merge IS on trunk and resumes by re-running instead of
227
+ implying nothing happened. When a concurrent land or drop finished the
228
+ workspace mid-merge, land answers `workspace_not_active` at exit 2 with
229
+ `pushed: true` and the `workspace drop` cleanup action — the merge is on
230
+ trunk, and re-running could never record it.
231
+ - **`status` stops labeling a feature branch's sync line "Trunk".** A
232
+ non-default branch renders as `Branch sync`, and `json.trunk` gains
233
+ `branch`/`isTrunk` (`isTrunk: null` when the default branch is unknown —
234
+ GitHub-owned source or an unborn cloud repo — never a guess), so
235
+ `trunk.state: "in_sync"` on a feature branch can no longer read as "local
236
+ trunk matches cloud trunk". `activity` (CLI and dashboard) stops printing
237
+ a fabricated "(0 files vs base)" changed-file count on workspace syncs —
238
+ the event never carried one.
239
+
240
+ - `deepspace workspace drop` and `land` no longer treat the generic `conflict`
241
+ error as "already finished".
242
+
243
+ The server refuses a finished workspace with `workspace_not_active`; `conflict`
244
+ was the pre-rename slug, and the CLI accepted both. Platform workers deploy
245
+ ahead of the CLI release, so nothing still answers with the old slug — and
246
+ `conflict` remains live for workspace-id clashes, which were being re-read as
247
+ "already finished" instead of surfacing. An id clash now raises immediately.
248
+
3
249
  ## 0.16.0
4
250
 
5
251
  ### Minor Changes
@@ -20,7 +266,7 @@
20
266
 
21
267
  ### Minor Changes
22
268
 
23
- - **`deepspace app migrate` is removed**, replaced by `deepspace app update` (below). It existed for one historical cutover — moving an app from a name-shaped id to a canonical one — and the platform endpoints behind it are unchanged, so an app still on a legacy id migrates with `npx deepspace@0.13.0 app migrate` and then upgrades normally.
269
+ - **`deepspace app migrate` is removed**, replaced by `deepspace app update` (below). It existed for one historical cutover — moving an app from a name-shaped id to a canonical one. The platform migration endpoints were subsequently removed too; an unexpected legacy-id app now requires operator-owned recovery. Do not downgrade to run the old command.
24
270
 
25
271
  A file that no longer exists now answers `404`, not the app's HTML. Deploys configured the asset layer with `not_found_handling: "single-page-application"`, so it answered EVERY unmatched path with `index.html` at 200 — correct for a client route, wrong for a file. A deploy replaces the hashed build chunks, so a tab still holding the previous `index.html` requested one and got HTML where JavaScript belonged: the script tag parsed it, failed, and the page went white with no error in any log, monitor, or network panel. The same answer went to agents probing `/llms.txt` and `/.well-known/mcp`, telling them the app publishes a manifest it does not have.
26
272
 
package/README.md CHANGED
@@ -6,7 +6,8 @@
6
6
 
7
7
  The DeepSpace SDK — build real-time collaborative apps on Cloudflare Workers.
8
8
  Bundles auth, real-time data subscriptions, RBAC, messaging, file storage,
9
- collaborative editing (Yjs), and zero-config deployment behind four imports.
9
+ collaborative editing (Yjs), and zero-config deployment through focused public
10
+ entry points.
10
11
 
11
12
  The fastest way to start is to scaffold a full app rather than wire the SDK up
12
13
  by hand:
@@ -27,15 +28,18 @@ npm install deepspace
27
28
 
28
29
  ## Entry points
29
30
 
30
- The package has four supported import paths:
31
+ The package has seven supported import paths:
31
32
 
32
33
  - **`deepspace`** — the React client SDK (hooks, providers, auth, storage,
33
34
  messaging, theme). Runs in the browser.
35
+ - **`deepspace/schema`** — schema builders and shared schema types.
34
36
  - **`deepspace/worker`** — the Cloudflare Worker runtime (`RecordRoom`, schemas,
35
37
  JWT verification, HMAC auth). Runs in your app's Worker.
36
38
  - **`deepspace/server`** — app-server helpers for actions, billing, and room
37
39
  handlers.
38
40
  - **`deepspace/testing`** — Playwright fixtures for multi-user tests.
41
+ - **`deepspace/documentation`** — documentation compiler and runtime helpers.
42
+ - **`deepspace/documentation/react`** — documentation React components.
39
43
 
40
44
  ## Minimal usage
41
45
 
@@ -89,11 +93,10 @@ npx deepspace dev start # run locally
89
93
  npx deepspace deploy # deploy to *.app.space
90
94
  ```
91
95
 
92
- The [normative CLI hierarchy](../../docs/platform/cli-contract.md#public-command-hierarchy)
93
- keeps durable app lifecycle and version migrations under `deepspace app`,
94
- while checkout-oriented Git, workspace, release, and deploy operations stay
95
- top-level. In particular, app migration is
96
- `deepspace app migrate`; there is no top-level `deepspace migrate` alias.
96
+ The hierarchy shown by `deepspace --help` keeps durable app lifecycle under
97
+ `deepspace app`, while checkout-oriented Git, workspace, release, and deploy
98
+ operations stay top-level. The historical `deepspace app migrate` command was
99
+ removed in 0.15.0; it is not an upgrade or recovery path.
97
100
 
98
101
  Every app has one authoritative Git repository. DeepSpace source is the packaged
99
102
  default: the first normal deploy claims it and publishes automatically.
@@ -114,40 +117,14 @@ DeepSpace verifies GitHub but never writes it. Inspect or transfer authority wit
114
117
  `deepspace app source`, `deepspace app source github`, or
115
118
  `deepspace app source deepspace`. Transfers mirror branches and tags before one
116
119
  atomic authority change; switching back uses the same commands. Commands support
117
- `--json` for agents. See the
118
- [repository guide](https://github.com/deepdotspace/deepspace/blob/main/docs/platform/repo-store-git.md)
119
- for workspaces, releases, and rollback.
120
-
121
- `deepspace app migrate` is the permanent upgrade runner for breaking app
122
- changes. It contains an ordered set of structural, idempotent migrations, so
123
- agents and developers keep using the same command across releases:
124
-
125
- ```bash
126
- npx deepspace app migrate --dry-run
127
- npx deepspace app migrate
128
- ```
129
-
130
- The dry-run lists pending source migrations without changing files. For a
131
- legacy GitHub app whose `DEEPSPACE_APP_ID` is still name-shaped, it also lists
132
- the exact registry rows that will be re-keyed and the physical stores that
133
- remain at the existing resource id. The command applies only transformations
134
- it recognizes safely, pauses for normal commit/push, and finishes with one
135
- deploy. Applied steps live in the checked-in `deepspace.migrations.json`
136
- manifest. Normal deploy bundles record that manifest in the release, so the
137
- next run can determine whether the migration is live without depending on Git
138
- commit lineage. Rerun after each returned action; when nothing is pending it
139
- reports `up_to_date`. This is the same workflow for GitHub and DeepSpace
140
- source; only their normal push behavior differs. `APP_NAME`-based
141
- legacy room and storage addresses are retained; canonical `DEEPSPACE_APP_ID`
142
- is used only for logical identity and platform authentication.
143
-
144
- Keep deploy, release rollback, and undeploy idle from the mutating command until
145
- that returned deploy begins; normal app traffic continues throughout.
146
- Before the registry cutover commits, `--cancel` reverses a prepared migration
147
- after the restored legacy configuration is committed and pushed. After the
148
- cutover, recovery is deliberately forward-only: deploy the canonical app and
149
- rerun `deepspace app migrate` to verify the live release. DeepSpace never
150
- writes the GitHub repository.
120
+ `--json` for agents. Use `deepspace --help`, command-specific `--help`, and the
121
+ [public manual](https://documentation.deep.space) for workspaces, releases, and
122
+ rollback.
123
+
124
+ Use the current command-specific release notes for supported upgrade steps. If
125
+ an app or checkout still carries a name-shaped legacy id, stop and contact the
126
+ DeepSpace operator; do not downgrade the SDK or run migration commands copied
127
+ from historical changelogs and proposals.
151
128
 
152
129
  ## Debugging
153
130