@tinycloud/cli 1.1.0-beta.7 → 1.1.0-beta.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinycloud/cli",
3
- "version": "1.1.0-beta.7",
3
+ "version": "1.1.0-beta.8",
4
4
  "description": "TinyCloud CLI - manage data, delegations, and nodes from the terminal",
5
5
  "author": "TinyCloud, Inc.",
6
6
  "license": "EGPL",
@@ -34,7 +34,7 @@
34
34
  "@tinycloud/node-sdk": "3.1.0-beta.5",
35
35
  "@tinycloud/node-sdk-wasm": "1.7.6",
36
36
  "@tinycloud/sdk-core": "3.1.0-beta.4",
37
- "@tinycloud/operations": "0.4.0-beta.7",
37
+ "@tinycloud/operations": "0.4.0-beta.8",
38
38
  "chalk": "^5.3.0",
39
39
  "commander": "^14.0.0",
40
40
  "open": "^11.0.0",
@@ -23,7 +23,7 @@ TC="$(npm prefix --global)/bin/tc"
23
23
  - Scoped and device logins refuse a profile that holds a local owner key (`LOCAL_OWNER_PROFILE`): a local-owner-key posture, `authMethod: "local"`, or a stored private key, whatever the recorded posture. Use a separate profile.
24
24
  - A live, unexpired session is replaced only by a scope that keeps everything it holds for the same owner and lasts at least as long: renewing the same manifest, or widening a session the owner narrowed earlier. Anything else, including an approval the owner narrowed, one that signs a caveat (a restriction such as a tenant) onto an action the live session holds unrestricted, or a renewal that would end earlier, refuses with `SESSION_IN_USE` and saves nothing. Use a new profile; pass `--replace-session` only when replacing that session is intended. The check runs on the request before consent and again on the approved scope before saving.
25
25
  - Approval can take minutes. Before saving, the CLI re-reads the profile, key and session under the profile lock; if another login, key rotation or logout changed them meanwhile, it refuses with `PROFILE_CHANGED_DURING_LOGIN` and saves nothing. The CLI's profile, key and session writers take the same lock, and a failed commit write restores all three before the lock is released. If that restore fails too, the login fails with `PROFILE_STATE_INCONSISTENT` naming each failure; a crash between writes surfaces the same code on the next scoped login; check `tc --profile PROFILE context`, then use `--replace-session` or a new profile. The commit waits up to 45 s for the lock (a crashed holder's lock is reclaimed after 30 s), else `PROFILE_LOCK_TIMEOUT`; other commands that time out on the lock report the same code. Wait for the other tc or MCP process and retry.
26
- - The other profile writers follow the same discipline. Local `tc auth rotate` signs in first and replaces the session in that compare-and-commit, so a change meanwhile refuses with `PROFILE_CHANGED_DURING_LOGIN` and the previous session stays. `tc profile delete` waits for the profile lock and removes the profile's files while holding it; a name that is not one path segment fails with `INVALID_PROFILE_NAME`. When `tc auth import` bootstraps a delegate profile's first session, the bootstrap reads and writes under the lock and never replaces a session that appeared after the import checked for one (`PROFILE_CHANGED_DURING_IMPORT`); if the bootstrap itself fails, it is rolled back. If a request-bound import (an artifact with a `requestId`) then fails, its bootstrap is rolled back only if the profile still holds exactly what the bootstrap wrote; if another login, logout or profile update replaced it meanwhile, that newer state is kept and the error says so. Other import forms keep a successful bootstrap when a later step fails.
26
+ - The other profile writers follow the same discipline. Local `tc auth rotate` signs in first and replaces the session in that compare-and-commit, so a change meanwhile refuses with `PROFILE_CHANGED_DURING_LOGIN` and the previous session stays. `tc profile delete` waits for the profile lock and removes the profile's files while holding it; a key, session, delegation or permission-request write that was waiting for the lock meanwhile then fails with `PROFILE_NOT_FOUND` instead of recreating the profile (see REFERENCE.md, Profile lock). A name that is not one path segment fails with `INVALID_PROFILE_NAME`. When `tc auth import` bootstraps a delegate profile's first session, the bootstrap reads and writes under the lock and never replaces a session that appeared after the import checked for one (`PROFILE_CHANGED_DURING_IMPORT`); if the bootstrap itself fails, it is rolled back. If a request-bound import (an artifact with a `requestId`) then fails, its bootstrap is rolled back only if the session and the profile's session DID and space still hold exactly what the bootstrap wrote (other profile changes made meanwhile, such as a new default space, are kept); if another login, logout or profile update replaced them, that newer state is kept and the error says so. Other import forms keep a successful bootstrap when a later step fails.
27
27
 
28
28
  While waiting, the command writes one approval line to stderr and keeps polling for the whole approval window (about 10 minutes), riding out dropped connections:
29
29
 
@@ -227,6 +227,8 @@ KV keys cannot contain spaces or control characters: the SDK sends keys unescape
227
227
  ```
228
228
  ~/.tinycloud/ # 0700
229
229
  ├── config.json # Global config (defaultProfile)
230
+ ├── profile-locks/
231
+ │ └── {name}/ # Turn lock (see below); kept after `tc profile delete`
230
232
  └── profiles/
231
233
  └── {name}/ # 0700
232
234
  ├── profile.json # Host, DID, chainId, ownerDid, spaceId (0600)
@@ -235,9 +237,22 @@ KV keys cannot contain spaces or control characters: the SDK sends keys unescape
235
237
  ├── additional-delegations.json # Imported and granted delegations, with request bindings (0600)
236
238
  ├── auth-requests.json # Stored permission requests (0600)
237
239
  ├── delegation-binding-migration.json # Present once unbound records were migrated (0600)
240
+ ├── .lock/ # Lock shared with older releases, while held
238
241
  └── cache/
239
242
  ```
240
243
 
244
+ ### Profile lock
245
+
246
+ Every write to a profile (key, session, settings, delegations, permission requests) runs under the profile's lock, shared by `tc` and the MCP server. Processes of this release first take a turn in `profile-locks/{name}/`, then the `profiles/{name}/.lock` directory every release uses, so they keep excluding CLI 1.0.0-beta.16 and older (which hold `.lock` from its creation) and 1.0.0-beta.17 … 1.0.1-beta.4 (which hold it once `owner.json` is linked in), and those exclude them. Among processes of this release, mutual exclusion holds however long any of them is paused, and needs no hard links (FAT/exFAT and SMB mounts work).
247
+
248
+ That guarantee assumes every process using the profile runs on the same host and in the same PID namespace (a crashed process is recognised by its PID being gone), and a filesystem whose `rename` of a directory fails when the target exists. It does not hold for a `TC_HOME` shared across machines (NFS) or between containers that do not share a PID namespace: there a live process can look crashed, have its turn or staging directory removed, and the lock can be taken twice or stop working. Older releases running at the same time keep their own limits: a 1.0.0-beta.16-or-older process paused for longer than 30 s right after creating `.lock`, or a 1.0.0-beta.17 … 1.0.1-beta.4 process reclaiming `.lock` at the same moment as such a process, can still let two writers in. CLI 1.0.0-beta.16 and older never reclaim an abandoned `.lock` without an owner record; a newer release does, once it is 30 s old.
249
+
250
+ A crashed holder's lock is reclaimed automatically (`.lock` once 30 s old). With hard links, dead-owner recovery keeps a `.recover-*` claim inside `.lock` until it finishes; it never unlinks or moves a newly published live owner, and a failed claim never removes another writer's empty `.lock`. A crashed current-release recoverer may leave only a `.recover-*` claim. Releases through 1.0.1-beta.4 cannot reclaim it even after it ages. A current-release command that acquires the affected profile's lock processes the claim, restoring a live owner's record if it contains one.
251
+
252
+ On either recovery path, an orphaned `.recover-*` claim can hold a replacement owner's live record: the hard-link path may claim it before detecting a mismatch, and the no-hard-links path may move it before putting it back. If the recoverer crashes and that holder releases while its process stays alive, the next acquisition restores the claimed record as `owner.json`. Acquisition can then remain blocked until the PID exits **and** the record is at least 30 s old—for a long-lived MCP server, possibly until it restarts. This costs availability only; it never admits a second holder. To clear the stalled profile manually, remove its `profiles/{name}/.lock` **only when no `tc` or MCP process is running**.
253
+
254
+ `PROFILE_LOCK_TIMEOUT` naming `profile-locks/{name}` means its turn lock is damaged (for example by a disk error): when no `tc` or MCP process is using that profile, remove `~/.tinycloud/profile-locks/{name}`; it is recreated on the next write. A write that waited for the lock while `tc profile delete` removed the profile fails with `PROFILE_NOT_FOUND` instead of recreating it.
255
+
241
256
  ## DID Formats
242
257
 
243
258
  - **Session key**: `did:key:z6Mk...#z6Mk...` — generated at init