@tinycloud/cli 1.2.0-beta.6 → 1.2.0-beta.7

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.2.0-beta.6",
3
+ "version": "1.2.0-beta.7",
4
4
  "description": "TinyCloud CLI - manage data, delegations, and nodes from the terminal",
5
5
  "author": "TinyCloud, Inc.",
6
6
  "license": "EGPL",
@@ -35,7 +35,7 @@
35
35
  "@tinycloud/node-sdk": "3.2.0-beta.5",
36
36
  "@tinycloud/node-sdk-wasm": "1.7.7",
37
37
  "@tinycloud/sdk-core": "3.2.0-beta.5",
38
- "@tinycloud/operations": "0.5.0-beta.5",
38
+ "@tinycloud/operations": "0.5.0-beta.6",
39
39
  "chalk": "^5.3.0",
40
40
  "commander": "^14.0.0",
41
41
  "open": "^11.0.0",
@@ -354,6 +354,7 @@ KV keys cannot contain spaces or control characters: the SDK sends keys unescape
354
354
  ├── config.json # Global config (defaultProfile)
355
355
  ├── profile-locks/
356
356
  │ └── {name}/ # Turn lock (see below); kept after `tc profile delete`
357
+ │ └── .live-<dir>-<id> # Socket of an acquisition in progress (Linux)
357
358
  └── profiles/
358
359
  └── {name}/ # 0700
359
360
  ├── profile.json # Host, DID, chainId, ownerDid, spaceId (0600)
@@ -370,7 +371,18 @@ KV keys cannot contain spaces or control characters: the SDK sends keys unescape
370
371
 
371
372
  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).
372
373
 
373
- 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.
374
+ 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. The one case this release handles across PID namespaces is a record that carries the checking process's own PID, below. 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.
375
+
376
+ A record with the checking process's own PID says nothing by its PID: a restarted container's `tinycloud-mcp-http` is PID 1 again, and so is a live MCP server in another container sharing the volume. Owner records written by this release therefore also carry a process-wide instance ID and, on Linux, the kernel boot ID, PID namespace and process start time. Each acquisition on Linux also listens on a UNIX socket in its turn directory, `profile-locks/{name}/.live-<dir>-<id>`, whose name records that directory's device and inode, from before its first turn record until it has released the lock; the socket appears under that name only once it accepts connections. A record with this process's PID and another instance ID counts as abandoned only if it comes from the same kernel boot and one of these holds:
377
+
378
+ - It comes from the same PID namespace with a different process start time: the PID was reused. This needs no socket, and works under Bun too.
379
+ - It comes from another PID namespace, its socket was bound in the very turn directory this process uses (the same device and inode, so a bind mount of that directory qualifies), and either the socket is gone or the kernel refuses connections to it. Only the holder removes its socket, after releasing, or a process that saw it refused; the kernel refuses connections only once the holder has closed the socket or exited, so a live holder, paused or not and under any hostname, is waited for. A refusal counts only if this process can then connect to its own socket in the same directory. That catches a sandbox that refuses every connection, such as a seccomp filter returning ECONNREFUSED for all connects. It does not catch a sandbox that fabricates refusals selectively: a seccomp filter keyed on the call's arguments, a seccomp user-notification supervisor, or a BPF LSM program. The lock trusts the kernel's answers here, as its PID check trusts kill(2). A sandbox that fakes either answer can make a live holder look gone, and is unsupported. Under Bun a refusal never counts, because Bun reports a refused connection as a missing file; a removed socket does.
380
+
381
+ Every other same-PID record is waited for, as before: records from older releases; a different boot ID (the host or VM rebooted, or the volume moved to another machine); a holder that could not create its socket (a filesystem without socket files, no `/proc`); a socket bound in a different turn directory than the one this process uses (`profile-locks/` not shared while `profiles/` is, a turn directory removed and recreated, or one reached through a mount with different device numbers, such as a second mount of a network share); a checking process that cannot create its own socket or connect to it; refusals seen under Bun; and macOS and Windows. If such a record's process is gone, writes to that profile fail with `PROFILE_LOCK_TIMEOUT` (`ProfileLockTimeoutError`), without the damaged-turn detail, until it is cleared. **Only while no `tc` or MCP process uses that profile**, remove its turn directory and its `.lock`:
382
+ - `tc` and the stdio MCP server (`tinycloud-mcp`): `$TC_HOME/.tinycloud/profile-locks/{name}` and `$TC_HOME/.tinycloud/profiles/{name}/.lock` (without `TC_HOME`, `~/.tinycloud/…`).
383
+ - The HTTP MCP server (`tinycloud-mcp-http`, the container image), for each affected tenant: `$TC_MCP_STATE_DIR/tenants/{tenant}/.tinycloud/profile-locks/agent` and `$TC_MCP_STATE_DIR/tenants/{tenant}/.tinycloud/profiles/agent/.lock` (`TC_MCP_STATE_DIR` is `/var/lib/tinycloud-mcp` in the image).
384
+
385
+ Never remove a `.live-*` socket or a turn directory while a process may hold the lock: a removed socket counts as a finished holder. Sockets left by processes that are gone are removed by a later acquisition (under Node) once the kernel refuses them.
374
386
 
375
387
  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.
376
388