Skip to content

Commit 10b0f55

Browse files
committed
docs: describe the remote publish client and status codes
1 parent 047f4c7 commit 10b0f55

4 files changed

Lines changed: 94 additions & 16 deletions

File tree

docs/architecture.md

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ crates/
5555
cabin-registry-file/ local file-registry layout, atomic writes, lock
5656
cabin-index-http/ sparse HTTP index client (read-only)
5757
cabin-credentials/ registry token storage (credentials.toml, -Z remote-registry)
58+
cabin-registry-api/ remote registry API client (publish / yank, -Z remote-registry)
5859
cabin-vendor/ typed VendorPlan + file-registry materialiser
5960
cabin-test/ test-target plan + sequential runner
6061
cabin-explain/ typed model for `cabin tree` / `cabin explain`
@@ -338,6 +339,25 @@ normalized index origins, plus the `CABIN_REGISTRY_TOKEN` environment override a
338339
surfacing a warning (not an error) for an existing group/world-readable file;
339340
- not perform HTTP; the client crates receive tokens as typed values from the orchestration layer.
340341

342+
### `cabin-registry-api`
343+
344+
Owns the typed HTTP client for the experimental remote-registry *mutations*
345+
([`remote-registry.md`](remote-registry.md)): `PUT /api/v1/packages/<name>/<version>` with the
346+
crates.io-style length-prefixed metadata + archive frame, and
347+
`PATCH /api/v1/packages/<name>/<version>/yank`. Requests target the API origin a registry's
348+
`config.json` declares (`api`), carry the caller-supplied bearer token, and map the protocol's
349+
status codes (`200` no-op, `201` created, `400`, `401`, `403`, `404`, `409`) plus the
350+
`{"errors":[{"detail":"..."}]}` envelope into typed errors, degrading to the raw status when the
351+
envelope is malformed. The crate must:
352+
353+
- not stage, validate, or lint packages - it frames and ships bytes produced by `cabin-package` /
354+
`cabin-publish`;
355+
- not implement read routes; `config.json`, package metadata, and artifact downloads stay in
356+
`cabin-index-http`;
357+
- not resolve credentials itself - it receives an optional typed token from the orchestration
358+
layer, refuses cleartext `http` beyond loopback hosts, and never follows redirects;
359+
- never let token bytes surface through errors or `Debug` output.
360+
341361
### `cabin-port`
342362

343363
Owns the foundation-port recipe layer: parsing `port.toml`, the checksum-addressed port cache, and
@@ -363,7 +383,10 @@ deps in its validator and `cabin-publish` never archives them. See
363383

364384
Owns publish-workflow orchestration. Combines `cabin-package`'s [`stage`] entry point with
365385
`cabin-registry-file`'s atomic writers to publish a single-package source tree into a local file
366-
registry. The crate must:
386+
registry. The experimental remote publish path (behind `-Z remote-registry`) reuses the same
387+
staging step and the shared lint seam (`staged_lint_warnings`) with a baseline fetched over the
388+
sparse HTTP read path; its transport lives in `cabin-registry-api` and its orchestration in
389+
`cabin`. The crate must:
367390

368391
- not implement HTTP / sparse / OCI publish;
369392
- not implement server-side functionality;
@@ -1315,8 +1338,9 @@ if the recorded arrays differ from the active policy. `cabin metadata` adds two
13151338

13161339
Local override policy never enters published artifacts: `cabin-package` rejects manifests with a
13171340
non-empty `[patch]` table; `.cabin/config.toml` (which carries config patches + source replacement)
1318-
is already in `EXCLUDED_DIR_NAMES`. Git sources, vendoring, registry authentication, HTTP publish,
1319-
new registry protocols, and registry-server work all remain deferred.
1341+
is already in `EXCLUDED_DIR_NAMES`. Git sources and registry-server work remain deferred; the
1342+
authenticated remote-registry client - reads and publish - is an experimental track behind
1343+
`-Z remote-registry` ([`remote-registry.md`](remote-registry.md)).
13201344

13211345
Full protocol in [`patch-overrides.md`](patch-overrides.md).
13221346

@@ -1453,10 +1477,10 @@ The following are *not* part of this repository today:
14531477
[`registry-design.md`](registry-design.md). Source registries are local file directories or
14541478
sparse HTTP today.
14551479
- **No non-local registry control plane.** Every command that needs an index expects `--index-path
1456-
<dir>` or `--index-url <url>`. There is no default remote registry and no package upload over
1457-
the network. (An experimental remote-registry *client* is being built behind
1458-
`-Z remote-registry` - protocol, `cabin login` / `cabin logout`, and authenticated reads; see
1459-
[`remote-registry.md`](remote-registry.md).)
1480+
<dir>` or `--index-url <url>`. There is no default remote registry, and package upload over the
1481+
network exists only behind the experimental `-Z remote-registry` client - protocol,
1482+
`cabin login` / `cabin logout`, authenticated reads, and `cabin publish` against an HTTP index
1483+
source; see [`remote-registry.md`](remote-registry.md).
14601484
- **No account / ownership workflows.** Ownership, signing, package yanking, and restricted package
14611485
access are out of scope.
14621486
- **No administrative policy surfaces.**

docs/package-format.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
`cabin publish --dry-run` adds a "no registry was modified" report. The dry-run path is
1414
**local-only**: nothing is uploaded, no registry mutation happens, and `cabin publish` without
15-
`--dry-run` exits with a clear error unless a local `--registry-dir` is provided.
15+
`--dry-run` exits with a clear error unless a local `--registry-dir` is provided or, behind
16+
`-Z remote-registry`, an HTTP index source is in effect
17+
([`remote-registry.md`](remote-registry.md)).
1618

1719
## Source archive format
1820

@@ -184,8 +186,11 @@ cabin publish --dry-run \
184186
[--format human|json]
185187
```
186188

187-
Same defaults. `cabin publish` without `--dry-run` and without `--registry-dir` exits with `actual
188-
publishing requires --registry-dir, or use --dry-run`.
189+
Same defaults. `cabin publish` without `--dry-run`, without `--registry-dir`, and without an
190+
effective HTTP index source exits with a clear error naming both options. Behind
191+
`-Z remote-registry`, an HTTP index source (`--index-url` or the `[registry] index-url` config
192+
setting) publishes the same staged bytes remotely; see
193+
[`remote-registry.md`](remote-registry.md).
189194

190195
## Output layout
191196

docs/registry-design.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ domain model:
100100
- `cabin-registry-file` owns the local mutable file-registry layout;
101101
- `cabin-index-http` performs read-only HTTP fetches and hands the
102102
retrieved JSON to the same typed index model;
103+
- `cabin-registry-api` owns the experimental authenticated mutation
104+
routes (publish / yank, behind `-Z remote-registry`);
103105
- `cabin-artifact` verifies, caches, and extracts source archives
104106
without knowing whether bytes came from a file or HTTP.
105107

@@ -123,7 +125,14 @@ track:
123125
issued on the registry web UI at `<origin>/me`;
124126
- publishing uses `PUT /api/v1/packages/<name>/<version>` with a
125127
length-prefixed metadata + archive frame, and yanking uses
126-
`PATCH /api/v1/packages/<name>/<version>/yank`.
128+
`PATCH /api/v1/packages/<name>/<version>/yank`;
129+
- remote `cabin publish` (an HTTP index source without
130+
`--registry-dir`) reuses the local staging pipeline byte-for-byte:
131+
the same validation, the same publish lints, and the same
132+
deterministic archive plus canonical per-version metadata document
133+
as `cabin publish --registry-dir` - only the final write differs,
134+
going through the typed `cabin-registry-api` client to the API
135+
origin the registry's `config.json` declares.
127136

128137
The registry *service* itself - accounts, token issuance, storage -
129138
remains outside this repository.

docs/remote-registry.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ itself - accounts, token issuance, hosted storage - is not part of this reposito
1010
[`registry-design.md`](registry-design.md).
1111

1212
Client status today: the [`config.json` fields](#registry-configuration) below are recognized
13-
behind the flag (presence without `-Z remote-registry` fails the index load), and client-side token
13+
behind the flag (presence without `-Z remote-registry` fails the index load), client-side token
1414
handling is implemented - `cabin login` / `cabin logout` plus
15-
[authenticated reads](#client-side-token-handling). The publish / yank commands land incrementally
16-
on the same gate.
15+
[authenticated reads](#client-side-token-handling) - and so is
16+
[publishing](#publishing-from-the-client) (`cabin publish` against an HTTP index source). The
17+
yank *route* has a typed client (`cabin-registry-api`), but a `cabin yank` command has not landed
18+
yet.
1719

1820
## Registry configuration
1921

@@ -34,7 +36,7 @@ A remote registry serves the same registry-root layout as the sparse HTTP index
3436
| Field | Type | Default | Description |
3537
| --- | --- | --- | --- |
3638
| `auth-required` | bool | `false` | When `true`, **every** request to this registry - including `config.json` itself, package metadata, and artifact downloads - must carry `Authorization: Bearer <token>`. |
37-
| `api` | string | absent | Absolute base URL of the registry web/API origin, e.g. `"https://dev-registry.cabinpkg.com"`. Non-`http(s)` schemes and URLs with `userinfo` credentials are rejected, mirroring the index-URL hygiene of the sparse HTTP client. When absent, the API origin defaults to the index origin. |
39+
| `api` | string | absent | Absolute base URL of the registry web/API origin, e.g. `"https://dev-registry.cabinpkg.com"`. Non-`http(s)` schemes and URLs with `userinfo` credentials are rejected, mirroring the index-URL hygiene of the sparse HTTP client. The read routes never consult it. When absent, `cabin publish` fails with an error naming the field: mutation requests are only ever sent to an explicitly declared API origin. |
3840

3941
Both index parsers (the local `--index-path` loader and the sparse HTTP client) parse the fields
4042
unconditionally, but *presence* of either field without `-Z remote-registry` fails the index load
@@ -157,7 +159,43 @@ Server-side behavior is part of the contract:
157159
- **Idempotency.** Re-publishing a version with byte-identical metadata and archive succeeds with
158160
`200` and body `{"ok":true,"no_op":true}`. Publishing the same version with *different* bytes is
159161
rejected with `409`.
160-
- A first-time publish succeeds with `200` and body `{"ok":true}`.
162+
- A first-time publish succeeds with `201` and body `{"ok":true}`.
163+
164+
### Publishing from the client
165+
166+
`cabin publish` targets a remote registry when the effective index source is an HTTP URL
167+
(`--index-url`, or the `[registry] index-url` setting in [`config.md`](config.md#registry)) and no
168+
`--registry-dir` is given. Without `-Z remote-registry` that combination fails with the standard
169+
experimental-feature error. The flow is log in once, publish, then resolve like any consumer:
170+
171+
```console
172+
$ echo "$TOKEN" | cabin -Z remote-registry login --index-url https://dev-registry.cabinpkg.com
173+
visit https://dev-registry.cabinpkg.com/me to create a token
174+
Login token for `https://dev-registry.cabinpkg.com` saved
175+
$ cabin -Z remote-registry publish --manifest-path fmt/cabin.toml \
176+
--index-url https://dev-registry.cabinpkg.com
177+
Published fmt 10.2.1 to https://dev-registry.cabinpkg.com
178+
checksum: sha256:...
179+
$ cabin -Z remote-registry resolve --manifest-path app/cabin.toml \
180+
--index-url https://dev-registry.cabinpkg.com
181+
```
182+
183+
Client-side behavior:
184+
185+
- The staging pipeline is the *same* one `cabin package` and the local
186+
`cabin publish --registry-dir` run - same validation, same publish lints
187+
([`package-format.md`](package-format.md)), same deterministic archive and canonical per-version
188+
metadata document. The uploaded bytes are byte-identical to what `cabin package` writes into
189+
`dist/` for the same source tree.
190+
- `config.json` (which supplies the [`api`](#registry-configuration) origin) and the lint baseline
191+
ride the authenticated read path; the upload carries the same bearer token to the API origin,
192+
under the same https-or-loopback cleartext rule.
193+
- On `201` the client reports the published name, version, and checksum. On `200` it reports that
194+
byte-identical bytes were already published and exits successfully - the same "re-running with
195+
identical input succeeds" semantics as the local flows. On `409` it explains that the version
196+
exists with different bytes and that published versions are immutable.
197+
- `--dry-run` stays entirely local: it stages into the output directory (default `dist/`) and
198+
never opens a connection.
161199

162200
## Yank
163201

@@ -179,6 +217,8 @@ succeeds with `200` and body `{"ok":true}`.
179217

180218
| Code | Meaning |
181219
| --- | --- |
220+
| `200` | Success without a state change: an idempotent no-op (byte-identical re-publish, or a yank set to the state the version already has). |
221+
| `201` | Publish of a version that did not exist before. |
182222
| `400` | Malformed request: bad framing, invalid metadata, or an invalid JSON body. |
183223
| `401` | No token or an invalid token (never reveals whether the package exists). |
184224
| `403` | Valid token, but the scope the route requires is missing. |

0 commit comments

Comments
 (0)