swarm-deploy 0.3.1 → 0.4.2

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
@@ -2,6 +2,71 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.4.2
8
+
9
+ - An explicit manual link request (`Client.link`, `swarm-deploy link`) now takes over an existing symlink at the requested name that has no ownership record, such as one made by hand before the storage root was managed. After the target and name pass every existing check, the symlink itself is replaced atomically by the requested link and a manual ownership record is written; the first request returns `LINKED` (even if the old link already read as the target) and repeats return `UNCHANGED`. The old link is never followed, so whatever it pointed at (a legacy directory, a dangling, absolute, or outside path, a symlink chain, a self-reference) is left untouched and never imported, and no commit metadata is invented for it. Previously such a request failed with `LINK_CONFLICT`. There is no new option, flag, wire field, or client change, so existing clients work unchanged, and ordered `afterLink` hooks, restart, and retention pinning behave as for any manual link, including a retry after a failed hook (`UNCHANGED`).
10
+ - Only symlinks are taken over. A regular file or directory at the name, a symlink whose record is automatic, version 1, malformed, or foreign, a manual link changed externally since it was recorded, and automatic link names still fail closed, and automatic reconciliation and startup never take a symlink over. The server re-checks that the symlink is unchanged (text, and inode where the storage reports one) and unrecorded immediately before the swap and fails closed otherwise. This is not a compare-and-swap, which the storage adapter does not offer, so do not edit a running server's storage root. A crash after the swap and before the record is repaired by repeating the same request.
11
+ - Case-insensitive volumes: a request for another spelling of a recorded link (`Latest` for a recorded `latest`, automatic or manual, including a link renamed in case outside the server) is one directory entry with an existing owner, so it fails with `LINK_CONFLICT` instead of replacing it and leaving two records. Before the swap the entry must be listed under exactly the requested spelling, and no protected name that differs only in case (a recorded link, a configured automatic link name even while its rule is dormant and has no record, or a managed artifact) may resolve to the same entry; it fails closed if the storage reports no inode identities while such a name exists. On a case-sensitive volume those names are distinct entries and stay independent.
12
+ - The `reset-storage` report note about remaining symlinks now says that an explicit link request replaces them while an automatic `--symlink` rule for that name still fails closed.
13
+
14
+ ## 0.4.1
15
+
16
+ - Release fix only; there is no runtime, API, or wire change. The `v0.4.0` publish workflow failed during action setup because its SHA-pinned `holepunchto/actions/publish` referenced `create-release@v1`, which no longer exists at `v1`. The `v0.4.0` tag is left as it is; use 0.4.1, which carries the 0.4.0 changes below.
17
+ - The CI and publish workflows now use the supported `holepunchto/actions/*@v1` pointer instead of commit SHAs, and publishing goes through the `publish@v1` alias. `npm run test:release-tag` now requires `@v1` on every Holepunch action reference. Tag and package validation, the `main` ancestry check, build-before-publish, the package checks, the `npm` environment, and OIDC provenance are unchanged.
18
+ - Documentation: the README is now a short entry point (features, requirements, install, identity model, quick start, and links). The detailed CLI, runtime API, operations and storage, artifact pattern, managed symlink, and hooks references moved to guides under `docs/` on GitHub, which the npm package does not include; the README links to them by URL. The protocol specification is unchanged.
19
+
20
+ ## 0.4.0
21
+
22
+ - Add the optional `afterLink` server hook (`ServerOptions.hooks`, and `--hooks <module>` modules) so a link change can be published. It runs after a managed link is durable, for an upload that moved an automatic link (after `afterCommit`, once per link that now points at the uploaded artifact, including an already-committed retry) and for an explicit `Client.link` / `swarm-deploy link` that passed every policy check. Its context is the secret-free, frozen `{ link: { name, target, mode, path, targetPath }, artifact, status, trigger }`, exported as `AfterLinkContext`, `HookLink`, `HookLinkMode`, `HookLinkStatus` and `HookLinkTrigger`.
23
+ - The client receives no success (`COMMITTED`, `ALREADY_COMMITTED`, `LINKED`, `UNCHANGED`) until the applicable `afterLink` calls return. A failing hook reaches the caller as the existing sanitized `HOOK_FAILED` code in the ordinary `FAILED` link result, upload final, or admission record, so clients and the wire format are unchanged, and `swarm-deploy link` now prints `HOOK_FAILED`. The caller sees that code only while it is still waiting: a client waits at most its idle timeout (60 s by default, 5 min at most) for the whole answer, which covers verification and commit, `afterCommit`, every `afterLink` call one after another, hooks queued ahead for the same link, and retention. If that expires first the client reports `UPLOAD_IDLE_TIMEOUT` instead, while the server still finishes its hooks. The durable link and artifact are never rolled back. Hooks must be idempotent on `link.name` plus `artifact.transferId`: repeating an unchanged link request (`UNCHANGED`) or retrying an upload re-runs the hook, so a failed publication can be retried without changing the link.
24
+ - A failed `afterLink` is observable server-side: `onFailure` gets the new `afterLink` `HookFailureContext.phase` (with `link`, and the link path as `path`) after the client was answered, a `failure` event and a `Link hook failed` warning are emitted, and a failed link request's `link` event carries the new optional `result` (`LINKED` or `UNCHANGED`) with `reason: 'HOOK_FAILED'`.
25
+ - Hooks run after the storage-root lease is released, so a slow hook never blocks commits, retention, recovery, or other links and cannot deadlock on the lease. The context is therefore a snapshot: a later decision may already have repointed the link, and retention may have removed an unpinned previous target. For one link name, hooks start in decision order and never overlap (an in-memory first-in, first-out line reserved under the lease; it does not survive a restart, and there is still no hook timeout). Hooks do not run for rejected or unauthorized link requests, for a manual link whose target is replaced, or for startup, scheduled, or retention reconciliation.
26
+ - **Fix.** After a `HOOK_FAILED` answer (any hook phase) the server now ends its write side and destroys the connection once the peer closes it, or after 5 seconds, instead of destroying it right behind the write. Over a real stream the immediate destroy could reset the connection before the client read the record, so the caller saw a connection reset instead of `HOOK_FAILED`. Other failure answers still close immediately and are unchanged.
27
+ - Configuring `afterLink` defers the post-commit retention pass until the hooks succeed, like `afterCommit`, so this commit's own link reconciliation runs before this commit's retention pass. That narrows, but does not remove, the chance that the link already moved: another commit's retention or reconciliation can do it first, and the hook then sees `UNCHANGED`. `status` is the link decision the call observed (`LINKED` when it created or repointed the link or repaired an automatic link's ownership record, `UNCHANGED` otherwise, including a manual ownership-metadata refresh for the same target), never proof of publication. Servers without `afterLink` are unchanged, including `afterCommit` ordering and its deferred retention.
28
+ - Add the runnable, **Node-only** `examples/link-publisher` hooks module and publisher (they use `node:child_process` and the global `process`, so they do not run on Bare; library hooks themselves work on Node and Bare). Each link change is passed as one JSON document on stdin to a script started without a shell: `SWARM_DEPLOY_PUBLISHER` names a JavaScript module file run with the current Node executable, not an arbitrary executable. Each call has a 20 s deadline: the publisher then gets `SIGTERM` and, 5 s later, `SIGKILL` (that direct child only), and the call always rejects with a timeout, even if the publisher exits zero after `SIGTERM`. It settles only once the child has exited, so an abandoned publisher cannot write over the next publication of the same link, and a call takes at most 25 s. The example's header shows how to size the client idle timeout for several sequential calls (25 s each).
29
+ - Add `swarm-deploy reset-storage --storage <dir> [--apply [--wait <ms>]]`. Without `--apply` it is a read-only preview of the
30
+ private state under one storage root: commit records and how many have no
31
+ payload, sessions, journals, staging, publications, the managed-link ledger,
32
+ trash, lock residue, earlier archives, and the visible payloads and symlinks
33
+ that would remain. With `--apply` it takes the storage lock, renames those
34
+ directories and lock residue into
35
+ `.swarm-deploy/archive/reset-<timestamp>-<random>/` with a `reset.json`
36
+ marker, recreates them empty, and releases the lock. Nothing is copied or
37
+ deleted; an archive is restored by moving it back.
38
+ - The reset never signals a process. A live lock holder makes `--apply` fail
39
+ closed with exit `1`; `--wait` polls until the holder releases the lock, a
40
+ holder whose PID is gone is retired automatically. Live and unreadable
41
+ locks cannot be bypassed by a PID override.
42
+ - The report states what still blocks a true fresh start: remaining payloads
43
+ keep their names (`FILE_EXISTS`) and remaining symlinks lose their ownership
44
+ record (`LINK_CONFLICT`). `fresh start: yes` is printed only for a root with
45
+ nothing visible left. Unsafe roots, symlinked internal paths, filesystem
46
+ roots, and uninitialized roots are rejected with exit `2` before any change.
47
+ - Block server recovery after an interrupted reset until a reset retry
48
+ completes, reject symlink ancestors, and refresh retained-file reporting
49
+ after waiting for exclusive access.
50
+ - A retry after an interrupted reset, including a crash or power loss that
51
+ skips error handling, resumes the archive the pending marker names instead of
52
+ creating a second one. Progress is reconciled from what the archive holds,
53
+ and a retry fails closed when the archive and the live directory both hold
54
+ data or the named archive is gone. One reset always ends as one archive, so
55
+ restoring it restores everything.
56
+ - Keep the archive marker bounded. `reset.json` is version 2: it names the
57
+ metadata directories, summarizes lock residue as a count and a SHA-256 of the
58
+ sorted names, and keeps the first attempt, the newest 16 attempts, and a total
59
+ count with failure text clipped. A marker therefore stays readable after any
60
+ number of stopped servers or retries, instead of becoming unreadable once its
61
+ lock list outgrew the reader. Version 1 markers stay readable at the shared
62
+ metadata size bound, a retry that resumes one first preserves it verbatim as
63
+ `reset.v1.json`, and the preview counts lock residue rather than listing it.
64
+ - Document that `reset-storage` must run as the server's operating-system user:
65
+ applying it as another user, such as `root`, leaves metadata the service
66
+ cannot use. The command does not enforce this.
67
+ - Export `resolveLayout`, `inspectStorageLock`, and `isProcessAlive` from the
68
+ storage layout module for the reset; existing lock behaviour is unchanged.
69
+
5
70
  ## 0.3.1
6
71
 
7
72
  - Normalize verified published artifact permissions before they become visible: