deepspace 0.12.0 → 0.14.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,167 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Enforce the per-tier storage quota on app files. An app's file allocation had per-file, per-part and per-request ceilings but no admission against the owner's plan, so a free-tier app could hold 202 MiB against a 128 MiB limit with every upload reporting success and no surface reporting usage. One per-tier table now serves both storage allocations (the repo store and app files), and the shared files handler admits every write: the single-request upload before the put, a chunked upload against its declared total at init, and again against the object R2 actually assembled at complete — an over-limit assembly is deleted rather than left in the allocation. An upsert is charged only for the bytes it adds. The owner's tier caps the allocation whoever writes, so an app writing at runtime and its owner's CLI meet the same limit; a billing lookup that fails refuses writes rather than admitting unmetered storage, and leaves reads working. Refusals answer `storage_quota_exceeded` with the used and limit byte counts and name the recovery, and `deepspace app files list` now prints storage used against the limit.
8
+ - `deepspace deploy` and `deepspace rollback` now wait for the release they just published, not merely for the app to answer. Every upload carries a synthetic `/.well-known/deepspace/release.json` asset naming its own version, injected by the platform at upload time — so this holds for every app already deployed, including rollbacks of bundles that predate the change, with no app code and no redeploy. Cloudflare serves assets atomically with the version, so the CLI polls that path until the edge returns the release it shipped; without a stamp (an older platform, or a resumed activation) it falls back to the previous reachability probe. "Deployed!" previously landed 30–60 seconds before the edge agreed, which made any deploy-then-assert script flaky by construction.
9
+
10
+ Invites can now be accepted without the emailed link. `deepspace app collaborators invites` lists the invites waiting for your signed-in email and `deepspace app collaborators accept <app-id>` accepts one — the same authority the `/join` page enforces (the signed-in email must match the invited address), reachable from a terminal. Previously an invitee who had never signed in could reach a state with no way forward: signing in did not grant access, the CLI said it would, and only the emailed link could complete it. That CLI message now describes what actually happens.
11
+
12
+ ## 0.13.0
13
+
14
+ ### Minor Changes
15
+
16
+ - Raise the app-files ceiling to 1 GiB by giving uploads the same streaming
17
+ discipline the deploy asset transport already has.
18
+
19
+ **The 25 MiB per-file limit was never a product decision.** It was the size of
20
+ one HTTP request leaking out as a rule about files: the handler read the whole
21
+ body into a ~128 MiB isolate, so the body had to be small. Media above it had
22
+ nowhere to live — deploy assets refuse it by design and named app files as its
23
+ home, which app files then also refused.
24
+
25
+ **App files now upload the way deploy assets do.** A file above the part size
26
+ is uploaded through R2's native multipart API: `POST /api/files/multipart` →
27
+ `PUT /api/files/multipart/part` × N → `POST /api/files/multipart/complete`,
28
+ with `DELETE /api/files/multipart` abandoning one. Every part is piped into R2
29
+ through a `FixedLengthStream` with a required `Content-Length`, so the worker
30
+ holds no more than one chunk of one part and a truncated body fails the write
31
+ instead of landing a corrupt object. An upload is therefore bounded by the size
32
+ of one request, never by the size of the file. There is no second upload
33
+ system: the same shared handler, the same validation, and both mounts — the
34
+ app's `/api/files` and the owner's `/api/app-files/:appId`.
35
+
36
+ **A request that declares no length is refused with 411.** `Number(null)` is
37
+ `0`, so a chunked body would otherwise clear every size bound as "empty" and
38
+ then be read unbounded — and R2 cannot size a write it has no length for. One
39
+ helper asks that question for the control bodies and the part path alike.
40
+
41
+ **Small files still take one round trip.** The single-request path is
42
+ unchanged below the part size, and its behaviour on both mounts is covered by
43
+ regression tests.
44
+
45
+ **Limits now say which question they answer.** `MAX_APP_FILE_BYTES` is 1 GiB
46
+ (the largest file, checked at init against the declared total and again at
47
+ complete against what R2 assembled — an over-ceiling object is deleted, not
48
+ kept). `MAX_UPLOAD_REQUEST_BYTES` is 25 MiB (the most one request body may
49
+ carry). `UPLOAD_PART_BYTES` is 20 MiB — both the part size init advertises and
50
+ the hard bound on a part, because `MAX_UPLOAD_PARTS` (52) is computed against
51
+ it: admitting 25 MiB parts would have made the real in-flight bound 1300 MiB
52
+ while the advertised ceiling stayed 1 GiB.
53
+
54
+ **Both clients chunk automatically, with peak memory bounded by the part size
55
+ rather than by the file** — roughly 40 MB for a file of any size. `deepspace
56
+ app files put` reports progress per part; `useR2Files().upload` takes an
57
+ optional `onProgress`. Parts go sequentially, a retryable failure (429, 5xx, a
58
+ dropped connection) is retried **once** with the range re-read so a transient
59
+ on part 40 of 52 does not discard a gibibyte, and any other failure abandons
60
+ the session — so a retry never leaves parts holding the app's quota.
61
+
62
+ `uploadBase64` stays single-request, and its client-side budget is now derived
63
+ from the ENCODED body (`MAX_BASE64_UPLOAD_BYTES`, ~18.75 MiB decoded). Measured
64
+ against the decoded size it accepted payloads whose bodies were a third larger
65
+ than the server would ever take — a check that green-lit uploads guaranteed to 413.
66
+
67
+ **Failures are classified rather than relayed.** R2's multipart error codes
68
+ become honest answers: an unknown or expired session is a 404 telling the
69
+ caller to start a new upload, parts that cannot assemble are a 400 saying why,
70
+ a missing `Content-Length` is a 411, and only genuine faults are 5xx. A stolen
71
+ `uploadId` is inert — every call rebuilds the R2 key from the caller's own
72
+ authenticated prefix, so a session id from another app names an upload that
73
+ does not exist there.
74
+
75
+ ### Patch Changes
76
+
77
+ - Fix a set of CLI honesty defects found by two independent agent-experience
78
+ audits against 0.12.0 — cases where a command reported something the platform
79
+ was not doing.
80
+
81
+ Patch, not minor: every item is a bug fix. `dev start --host` is a new flag on
82
+ an existing command and `dev start --json` now emits the readiness envelope it
83
+ already advertised, so neither adds API surface a caller could not already ask
84
+ for. Under this repo's 0.x policy that stays patch-appropriate.
85
+ - **`status` no longer reports a live release for an undeployed app.** Liveness
86
+ came from the append-only release log, which undeploy never touches, so
87
+ `status` printed `Live release #4 · https://…` while the edge 404'd and
88
+ `app list` said `undeployed`. Both commands now read one predicate over the
89
+ registry row, and `app list` reports collaborated apps (with a ROLE column)
90
+ instead of hiding the apps a collaborator can deploy but never discover.
91
+ - **The scaffolder persists its git identity.** It committed with `git -c
92
+ user.name=… -c user.email=…` and wrote nothing to `.git/config`, so the next
93
+ commit — the one `deploy` requires — died `unable to auto-detect email
94
+ address` in any container without global git config.
95
+ - **`--help` is ANSI-free when stdout is not a terminal.** citty freezes its
96
+ no-color decision at module load and only honors `NO_COLOR=1`, never
97
+ consulting stdout; help now renders through one chokepoint that strips SGR
98
+ escapes off a pipe and leaves a terminal coloured.
99
+ - **`dev start --json` emits a real readiness envelope** the moment the port
100
+ answers, instead of nothing until the server exits, and gains `--host` for
101
+ binding a specific interface. Readiness and the port pre-check both probe by
102
+ CONNECTING: a bind probe answers "can I bind this address", which on
103
+ macOS/BSD reports 127.0.0.1 free while vite serves 0.0.0.0. A readiness
104
+ timeout emits `{"ok":false,"code":"dev_server_not_ready"}` rather than
105
+ nothing, so a caller blocking on that line cannot hang.
106
+ - **Generated apps reserve the agent-protocol paths.** `/llms.txt`,
107
+ `/llms-full.txt`, `/.well-known/mcp`, `/.well-known/mcp.json` and
108
+ `/.well-known/mcp/*` fell through to the SPA shell, so a probe read the
109
+ homepage as a published manifest. They are now in the deploy worker's
110
+ `run_worker_first` baseline (the only list that reaches Cloudflare — the
111
+ CLI's reserved list is a deny-list that strips them from the app's own
112
+ config), and the app's static fallback withholds the SPA shell for them. A
113
+ real `public/llms.txt` still serves; only the shell standing in for one is
114
+ withheld.
115
+ - **`rollback` waits for edge propagation** before claiming a URL is live,
116
+ reusing deploy's wait — now one implementation instead of a private copy.
117
+ - **Human output no longer prints raw user ids.** `whoami` drops the `UserID:`
118
+ line, `releases` resolves actors to emails the way `activity` already did,
119
+ and the on-behalf deploy notice states the attribution instead of an opaque
120
+ id. All ids stay in `--json`.
121
+ - **The oversize-push recipe works when executed verbatim.** `git reset --soft`
122
+ leaves the file STAGED, so the documented re-commit re-added the same blob and
123
+ the push failed identically; the recipe now includes `git restore --staged`,
124
+ says to MOVE the file out of `public/` (`.gitignore` does not exclude it from
125
+ the deploy bundle), and is covered by a test that runs the shipped sentence
126
+ step by step against a real remote.
127
+ - **Deploy validates asset sizes before it pushes.** The commit reached the
128
+ cloud repo first, so the repo advanced onto a release the platform then
129
+ refused; the refusal now names the file and its size in MiB rather than a
130
+ SHA-256 and a raw byte count. Only the PER-FILE cap is checked locally, from
131
+ a constant the deploy worker now imports rather than duplicates — the
132
+ per-deploy total is env-configurable and the server dedupes by content hash
133
+ before summing, so any local total would be a guess doing different
134
+ arithmetic.
135
+ - **`app files put` refuses active content.** `.svg`, `.html`, `.htm`, `.js`
136
+ and `.mjs` were missing from the type map, uploaded as
137
+ `application/octet-stream`, and were stored past the server's own 415 — then
138
+ served un-renderable under a `✓`. `.xml` is deliberately left unmapped so
139
+ sitemaps and RSS feeds keep working exactly as before.
140
+ - **`app files rm` reports a missing key** instead of printing `✓ deleted` for
141
+ a name that was never there. The HTTP contract is unchanged — DELETE stays
142
+ idempotent and 200, now carrying `existed`, because deployed apps branch on
143
+ `res.ok`; the refusal is the CLI's. The oversize code is `too_large` on both
144
+ sides rather than `too_large` from the worker and `file_too_large` from the
145
+ client for one condition.
146
+ - **Ceilings appear in `--help`** for `deploy`, `push` and `app files put`,
147
+ read from the constants rather than restated.
148
+ - **The unknown-command suggester withholds the executable action when the
149
+ guess is destructive** (`rm` → `app files rm`, `delete` → `secrets delete`).
150
+ The suggestion still appears in prose; only the runnable `action` is dropped.
151
+ A whole command passed as ONE quoted token (`deepspace "auth whoami"`, which
152
+ a shell that does not word-split produces) is now recognised and reported as
153
+ a quoting problem, instead of suggesting a command that printed identically
154
+ to what was typed.
155
+ - **The collaborator-invite failure says what actually failed** — email
156
+ delivery, with the invite not created and the charge voided — instead of a
157
+ bare "please try again" that loops forever on an undeliverable address.
158
+ - **MCP discovery advertises every protocol version the handshake accepts.**
159
+ The handshake itself was already spec-correct (a server MUST echo a supported
160
+ version the client requested); the card advertising only the newest is what
161
+ made the two look like they disagreed.
162
+
163
+ - Fix documentation sites built from MDX crashing on every client-side navigation. The article's imperative code-block pass reparented each compiled `pre`, which is invisible on the default runtime but detaches a React-owned node on the executable runtime, so the next route swap threw `NotFoundError: The node to be removed is not a child of this node` and tore the article out of the page. The prose subtree now names its single writer and the imperative passes cannot reach React-rendered nodes; MDX gets the same code-block and tab-group structure from React components instead. Also fixes the assistant chip clipping the site name, the search trigger rendering at the reading scale instead of the chrome scale, and the theme control buttons sitting edge to edge.
164
+
3
165
  ## 0.12.0
4
166
 
5
167
  ### Minor Changes