torb deprecate: flag a whole package as deprecated #279

Closed
opened 2026-10-09 20:26:36 +00:00 by orion · 9 comments
Collaborator

Goal

torb deprecate <owner/name> [--use <owner/name>] [--reason "..."] flags a whole package as deprecated, as
RELEASE.md section 7.3 describes: shown by torb add and on the package page, withdraws nothing. Split from #22.

Why it is its own issue

Unlike yank, the registry has no API for it: it needs an endpoint, a stored flag, an index record and a client display.

Scope

  • Registry: POST /api/1/packages/<owner>/<name>/deprecate {reason?, use?} and DELETE to undo, for a member with a new or existing action (decide: yank covers it unless a reason appears to split it); an event in the log.
  • Index: an appended, signed record (deprecate "acme/http", use: "acme/http2", reason: "..." and an undeprecate line) in the style of yank/unyank; readIndex and signing.trb handle it. Readers already skip a word they do not know (recordsOf), so a client without it keeps resolving; verify that with a test.
  • Client: torb deprecate (and --undo), torb add prints the notice, torb update/install stay silent about it (it withdraws nothing).
  • file: registry: appended unsigned like yank on a file: registry.

Acceptance criteria

  • torb deprecate and --undo round-trip against the registry over loopback (a temporary database and file: storage) and on a file: registry
  • The index record is signed and verified; an old reader ignores it (test with recordsOf on a file with the new line)
  • torb add acme/http prints the deprecation and its replacement; resolution is unchanged
  • Docs: docs/tooling/torb-deprecate.md, RELEASE.md 7.3/7.4 (index format), registry README API table; skills regenerated

Design record point

RELEASE.md 7.3 ("Deprecating a whole package") and 7.4 (the index lines). Seed impact: none (TorbScript only). Size: if the registry and the client do not fit one PR of two commits, split the client into a second issue.

Depends on #22 (the registry-resolution and token lookup it introduces).

## Goal `torb deprecate <owner/name> [--use <owner/name>] [--reason "..."]` flags a whole package as deprecated, as RELEASE.md section 7.3 describes: shown by `torb add` and on the package page, withdraws nothing. Split from #22. ## Why it is its own issue Unlike `yank`, the registry has no API for it: it needs an endpoint, a stored flag, an index record and a client display. ## Scope - Registry: `POST /api/1/packages/<owner>/<name>/deprecate {reason?, use?}` and `DELETE` to undo, for a member with a new or existing action (decide: `yank` covers it unless a reason appears to split it); an event in the log. - Index: an appended, signed record (`deprecate "acme/http", use: "acme/http2", reason: "..."` and an `undeprecate` line) in the style of `yank`/`unyank`; `readIndex` and `signing.trb` handle it. Readers already skip a word they do not know (`recordsOf`), so a client without it keeps resolving; verify that with a test. - Client: `torb deprecate` (and `--undo`), `torb add` prints the notice, `torb update`/`install` stay silent about it (it withdraws nothing). - `file:` registry: appended unsigned like `yank` on a `file:` registry. ## Acceptance criteria - [ ] `torb deprecate` and `--undo` round-trip against the registry over loopback (a temporary database and `file:` storage) and on a `file:` registry - [ ] The index record is signed and verified; an old reader ignores it (test with `recordsOf` on a file with the new line) - [ ] `torb add acme/http` prints the deprecation and its replacement; resolution is unchanged - [ ] Docs: `docs/tooling/torb-deprecate.md`, RELEASE.md 7.3/7.4 (index format), registry README API table; skills regenerated ## Design record point RELEASE.md 7.3 ("Deprecating a whole package") and 7.4 (the index lines). Seed impact: none (TorbScript only). Size: if the registry and the client do not fit one PR of two commits, split the client into a second issue. Depends on #22 (the registry-resolution and token lookup it introduces).
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop, split from #22), not a duplicate, no collision with a design record (RELEASE.md 7.3). Depends on #22 and needs refine (registry endpoint, stored flag, index record, client display). next: waiting (dependency #22)

[loop:torbscript] intake: in scope (author orion, label loop, split from #22), not a duplicate, no collision with a design record (RELEASE.md 7.3). Depends on #22 and needs refine (registry endpoint, stored flag, index record, client display). next: waiting (dependency #22)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-09T23:40Z (dependency #22 landed in 6f9d340b)

[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-09T23:40Z (dependency #22 landed in 6f9d340b)
Author
Collaborator

[loop:torbscript] refine: ready, one PR of two commits (read against main at 6f9d340b, after #22).

Goal. torb deprecate <owner/name> [--use <owner/name>] [--reason <text>] [--undo] flags a whole package as deprecated (RELEASE.md 7.3, "Deprecating a whole package"): torb add shows the notice, nothing is withdrawn, resolution and update/install are unchanged.

Acceptance criteria (testable, each with its test below)

  • POST /api/1/packages/<owner>/<name>/deprecate {reason?, use?} appends a signed deprecate line to the package's index file and stores the flag; a second POST replaces the notice (the last line holds); DELETE appends a signed undeprecate line and clears the flag, 409 when the package is not deprecated, 404 for an unknown package, 400 for a use that is not a package name or is the package itself, 401/403 like yank. An event deprecate/undeprecate is logged with the package as subject.
  • readIndex folds the two lines into PackageIndex.deprecation: IndexDeprecation? (use, reason); renderDeprecate/renderUndeprecate write them in the style of renderYank/renderUnyank.
  • Every record of a file that has deprecation lines verifies with verifyIndex, and a client that does not know the two words keeps verifying and resolving (see decision 4; tested with a frozen reader that skips the words).
  • torb deprecate and --undo round-trip against the write service over loopback (stored token, temporary database, file: storage) and on a file: registry (appended unsigned, refused where index/config.trb lists keys, exactly as yank).
  • torb add acme/http (also acme/http@^1) prints that the package is deprecated, the reason and the replacement; the lock is the one it would have written without the notice; torb update/torb install print nothing about it.
  • Usage errors exit 2 (no package, a package that is not owner/name, --undo with --use or --reason, an unknown flag, --use that is not a package name), refusals exit 1.
  • Docs: docs/tooling/torb-deprecate.md (+ index.md, the-torb-command.md, torb-yank.md "Related"), RELEASE.md 7.3/7.4/7.13/7.14, registry README (API table, "Not built yet" loses deprecate), skills/ regenerated.

Design record point. RELEASE.md 7.3 (the bullet "Deprecating a whole package"), 7.4 (the index lines), 7.14 (what a signature is over); ROADMAP.md section on the package manager only needs the one-line mention next to torb yank.

Seed impact: none. No syntax change, no new native, no driver flag, no std name looked up by string. The new IndexRecord cases and PackageIndex.deprecation are compiler-internal TorbScript; the registry builds them through the path: dependency on the compiler.

Size. One PR, two Conventional Commits:

  1. feat(registry): deprecate records in the index and the registry API (index.trb, signing.trb, registry service, API, database, their tests, README).
  2. feat(package): torb deprecate and the notice of torb add (CLI, usage, file: registry, add notice, docs, skills).

Files touched.

  • compiler/src/package/index.trb: IndexDeprecation, IndexRecord.Deprecate/Undeprecate, recordsOf/readIndex fold, recordMessage, renderDeprecate/renderUndeprecate, deprecatedIndex (like yankedIndex, for file:), a helper that counts old-kind positions.
  • compiler/src/package/signing.trb: verifyIndex position/sequence counters.
  • tools/registry/src/registry.trb (deprecate beside yank, positionIn), api.trb (route ["packages", owner, name, "deprecate"], DeprecateRequest), database.trb (columns and migration).
  • compiler/src/cli/registry.trb (deprecate command, --use), cli/usage.trb, main.trb (command list at the login|logout|owner|yank arm), cli/packages.trb (notice in runAdd), tools/command-line.sh, tests/command-line/arguments.expected.
  • Tests: compiler/tests/packages.test.trb, compiler/tests/registry-commands.test.trb, tools/registry/tests/server.test.trb, tools/registry/tests/commands.test.trb.
  • Docs: docs/tooling/torb-deprecate.md (new), docs/tooling/index.md, the-torb-command.md, torb-yank.md, docs/design/RELEASE.md, docs/ROADMAP.md, tools/registry/README.md, docs/skills/torbscript-projects/SKILL.md, regenerated skills/**, .pdisc-sidecar.yaml/INDEX.md as pdisc asks.

Test plan (forge CI, no local suites).

  • compiler/tests/packages.test.trb: render/read round trip; fold (deprecate, replace, undeprecate, a line naming another package is an error); verifyIndex on a file release, deprecate, yank, undeprecate, unyank with keys; a replayed older undeprecate and a moved deprecate are refused; a frozen "previous reader" (local function that skips the two words and counts only release/yank/unyank) computes the same positions and the same candidatesOf result.
  • compiler/tests/registry-commands.test.trb: option parsing and every usage error; the file: registry append, undo, signed-registry refusal.
  • tools/registry/tests/server.test.trb: the endpoint (success, replace, undo, each refusal above, token without yank refused, event logged, columns set and cleared), every record of the resulting file verifying.
  • tools/registry/tests/commands.test.trb: extend the loopback round: deprecate with the stored token, torb add prints notice and replacement and writes the same lock, --undo, token in no curl argument (existing assertions keep holding).
  • Tier A gates run by CI: command-line.sh against arguments.expected, docs skill --check, format --check, pdisc.

Implementer. opus / high (compiler/src and signed-index code of the registry).

Dependencies and overlap. #22 landed (6f9d340b). PR #282 (#276) edits tools/registry/src/registry.trb, api.trb, database.trb, tools/registry/README.md, docs/design/RELEASE.md, .pdisc-sidecar.yaml, INDEX.md: start from main after #282 merged, or rebase onto it before opening the PR. #280 edits compiler/src/cli/registry.trb, cli/usage.trb, RELEASE.md 7.13 and the registry README as well (textual overlap only; whichever lands second rebases).

[loop:torbscript] decided by the loop: the yank action covers deprecation, no new action. A new action would leave every existing token unable to deprecate, change the token/everyAction contract and the account page for one rarely used call; a deprecation is the same kind of power as a yank (a publisher or owner of the package). Authorization is mayWritePackage(caller, package, "yank").

[loop:torbscript] decided by the loop: record shape and semantics. deprecate "acme/http", use: "acme/http2", reason: "...", signature: "..." and undeprecate "acme/http", signature: "..." (the issue's form); the positional package must equal the file's package or readIndex fails. use and reason are both optional and trimmed; use must be a valid package name other than the package itself but need not exist on this registry (it may live elsewhere). The last of the two words holds; deprecating again replaces the notice (no 409), undoing when not deprecated is a 409.

[loop:torbscript] decided by the loop: signature positions, so old clients keep working. Today a record's signed position is the count of records recordsOf returned before it, and an old client counts only release/yank/unyank because it skips unknown words. If deprecation lines counted in position, every record after the first deprecate would fail verification in every existing torb (RELEASE.md 7.14: "a record the registry did not sign is never seen"), i.e. a deprecation would break resolution of that package for all current clients. Therefore: position counts only the three old kinds (so no existing signature changes), and a deprecation record is signed over torb index record 1, the package, position (old kinds before it), sequence (deprecation records before it), then its own fields. sequence stops a mirror from replaying an old undeprecate after a later deprecate; position pins it between the same neighbours. positionIn in the registry and verifyIndex count the same way. 7.14's "What a signature is over" row records this.

[loop:torbscript] decided by the loop: the stored flag is three columns on packages (deprecated, deprecated_use, deprecated_reason), added by an ALTER TABLE migration in the style of tokens.factor; the index stays the truth, as for yanks. Search results and the package page do not show it in this PR: the page is slice 13 and does not exist, search is #241.

[loop:torbscript] decided by the loop: client display. torb add prints one notice per package named on its command line (not transitive ones), after the resolution succeeded, from the index it already reads and verifies (the cached copy offline). update and install stay silent, as the issue says. No split of the client into a second issue: the pieces are small enough for the two commits.

next: implement

[loop:torbscript] refine: ready, one PR of two commits (read against main at 6f9d340b, after #22). **Goal.** `torb deprecate <owner/name> [--use <owner/name>] [--reason <text>] [--undo]` flags a whole package as deprecated (RELEASE.md 7.3, "Deprecating a whole package"): `torb add` shows the notice, nothing is withdrawn, resolution and `update`/`install` are unchanged. **Acceptance criteria** (testable, each with its test below) - [ ] `POST /api/1/packages/<owner>/<name>/deprecate {reason?, use?}` appends a signed `deprecate` line to the package's index file and stores the flag; a second `POST` replaces the notice (the last line holds); `DELETE` appends a signed `undeprecate` line and clears the flag, `409` when the package is not deprecated, `404` for an unknown package, `400` for a `use` that is not a package name or is the package itself, `401`/`403` like `yank`. An event `deprecate`/`undeprecate` is logged with the package as subject. - [ ] `readIndex` folds the two lines into `PackageIndex.deprecation: IndexDeprecation?` (`use`, `reason`); `renderDeprecate`/`renderUndeprecate` write them in the style of `renderYank`/`renderUnyank`. - [ ] Every record of a file that has deprecation lines verifies with `verifyIndex`, and **a client that does not know the two words keeps verifying and resolving** (see decision 4; tested with a frozen reader that skips the words). - [ ] `torb deprecate` and `--undo` round-trip against the write service over loopback (stored token, temporary database, `file:` storage) and on a `file:` registry (appended unsigned, refused where `index/config.trb` lists keys, exactly as `yank`). - [ ] `torb add acme/http` (also `acme/http@^1`) prints that the package is deprecated, the reason and the replacement; the lock is the one it would have written without the notice; `torb update`/`torb install` print nothing about it. - [ ] Usage errors exit 2 (no package, a package that is not `owner/name`, `--undo` with `--use` or `--reason`, an unknown flag, `--use` that is not a package name), refusals exit 1. - [ ] Docs: `docs/tooling/torb-deprecate.md` (+ `index.md`, `the-torb-command.md`, `torb-yank.md` "Related"), RELEASE.md 7.3/7.4/7.13/7.14, registry README (API table, "Not built yet" loses `deprecate`), `skills/` regenerated. **Design record point.** RELEASE.md 7.3 (the bullet "Deprecating a whole package"), 7.4 (the index lines), 7.14 (what a signature is over); ROADMAP.md section on the package manager only needs the one-line mention next to `torb yank`. **Seed impact: none.** No syntax change, no new native, no driver flag, no std name looked up by string. The new `IndexRecord` cases and `PackageIndex.deprecation` are compiler-internal TorbScript; the registry builds them through the `path:` dependency on the compiler. **Size.** One PR, two Conventional Commits: 1. `feat(registry): deprecate records in the index and the registry API` (index.trb, signing.trb, registry service, API, database, their tests, README). 2. `feat(package): torb deprecate and the notice of torb add` (CLI, usage, `file:` registry, `add` notice, docs, skills). **Files touched.** - `compiler/src/package/index.trb`: `IndexDeprecation`, `IndexRecord.Deprecate`/`Undeprecate`, `recordsOf`/`readIndex` fold, `recordMessage`, `renderDeprecate`/`renderUndeprecate`, `deprecatedIndex` (like `yankedIndex`, for `file:`), a helper that counts old-kind positions. - `compiler/src/package/signing.trb`: `verifyIndex` position/sequence counters. - `tools/registry/src/registry.trb` (`deprecate` beside `yank`, `positionIn`), `api.trb` (route `["packages", owner, name, "deprecate"]`, `DeprecateRequest`), `database.trb` (columns and migration). - `compiler/src/cli/registry.trb` (`deprecate` command, `--use`), `cli/usage.trb`, `main.trb` (command list at the `login|logout|owner|yank` arm), `cli/packages.trb` (notice in `runAdd`), `tools/command-line.sh`, `tests/command-line/arguments.expected`. - Tests: `compiler/tests/packages.test.trb`, `compiler/tests/registry-commands.test.trb`, `tools/registry/tests/server.test.trb`, `tools/registry/tests/commands.test.trb`. - Docs: `docs/tooling/torb-deprecate.md` (new), `docs/tooling/index.md`, `the-torb-command.md`, `torb-yank.md`, `docs/design/RELEASE.md`, `docs/ROADMAP.md`, `tools/registry/README.md`, `docs/skills/torbscript-projects/SKILL.md`, regenerated `skills/**`, `.pdisc-sidecar.yaml`/`INDEX.md` as `pdisc` asks. **Test plan (forge CI, no local suites).** - `compiler/tests/packages.test.trb`: render/read round trip; fold (deprecate, replace, undeprecate, a line naming another package is an error); `verifyIndex` on a file `release, deprecate, yank, undeprecate, unyank` with keys; a replayed older `undeprecate` and a moved `deprecate` are refused; a frozen "previous reader" (local function that skips the two words and counts only release/yank/unyank) computes the same positions and the same `candidatesOf` result. - `compiler/tests/registry-commands.test.trb`: option parsing and every usage error; the `file:` registry append, undo, signed-registry refusal. - `tools/registry/tests/server.test.trb`: the endpoint (success, replace, undo, each refusal above, token without `yank` refused, event logged, columns set and cleared), every record of the resulting file verifying. - `tools/registry/tests/commands.test.trb`: extend the loopback round: deprecate with the stored token, `torb add` prints notice and replacement and writes the same lock, `--undo`, token in no curl argument (existing assertions keep holding). - Tier A gates run by CI: `command-line.sh` against `arguments.expected`, `docs skill --check`, `format --check`, pdisc. **Implementer.** opus / high (compiler/src and signed-index code of the registry). **Dependencies and overlap.** #22 landed (6f9d340b). PR #282 (#276) edits `tools/registry/src/registry.trb`, `api.trb`, `database.trb`, `tools/registry/README.md`, `docs/design/RELEASE.md`, `.pdisc-sidecar.yaml`, `INDEX.md`: start from main after #282 merged, or rebase onto it before opening the PR. #280 edits `compiler/src/cli/registry.trb`, `cli/usage.trb`, RELEASE.md 7.13 and the registry README as well (textual overlap only; whichever lands second rebases). [loop:torbscript] decided by the loop: **the `yank` action covers deprecation, no new action.** A new action would leave every existing token unable to deprecate, change the token/`everyAction` contract and the account page for one rarely used call; a deprecation is the same kind of power as a yank (a publisher or owner of the package). Authorization is `mayWritePackage(caller, package, "yank")`. [loop:torbscript] decided by the loop: **record shape and semantics.** `deprecate "acme/http", use: "acme/http2", reason: "...", signature: "..."` and `undeprecate "acme/http", signature: "..."` (the issue's form); the positional package must equal the file's `package` or `readIndex` fails. `use` and `reason` are both optional and trimmed; `use` must be a valid package name other than the package itself but need not exist on this registry (it may live elsewhere). The last of the two words holds; deprecating again replaces the notice (no `409`), undoing when not deprecated is a `409`. [loop:torbscript] decided by the loop: **signature positions, so old clients keep working.** Today a record's signed `position` is the count of records `recordsOf` returned before it, and an old client counts only `release`/`yank`/`unyank` because it skips unknown words. If deprecation lines counted in `position`, every record after the first `deprecate` would fail verification in every existing `torb` (RELEASE.md 7.14: "a record the registry did not sign is never seen"), i.e. a deprecation would break resolution of that package for all current clients. Therefore: `position` counts only the three old kinds (so no existing signature changes), and a deprecation record is signed over `torb index record 1`, the package, `position` (old kinds before it), `sequence` (deprecation records before it), then its own fields. `sequence` stops a mirror from replaying an old `undeprecate` after a later `deprecate`; `position` pins it between the same neighbours. `positionIn` in the registry and `verifyIndex` count the same way. 7.14's "What a signature is over" row records this. [loop:torbscript] decided by the loop: **the stored flag** is three columns on `packages` (`deprecated`, `deprecated_use`, `deprecated_reason`), added by an `ALTER TABLE` migration in the style of `tokens.factor`; the index stays the truth, as for yanks. Search results and the package page do not show it in this PR: the page is slice 13 and does not exist, search is #241. [loop:torbscript] decided by the loop: **client display.** `torb add` prints one notice per package named on its command line (not transitive ones), after the resolution succeeded, from the index it already reads and verifies (the cached copy offline). `update` and `install` stay silent, as the issue says. No split of the client into a second issue: the pieces are small enough for the two commits. next: implement
Author
Collaborator

[loop:torbscript] implement: deferred until PR #282 lands (shared registry files, per the refine). next: waiting (PR #282 merge)

[loop:torbscript] implement: deferred until PR #282 lands (shared registry files, per the refine). next: waiting (PR #282 merge)
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T05:50Z (PR #282 landed as 765a6218, the dependency named in the refine)

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T05:50Z (PR #282 landed as 765a6218, the dependency named in the refine)
Author
Collaborator

[loop:torbscript] claim released by session:loop-torbscript-20261010: no worker was started (sessions_spawn was not available in this tick), so the claim until 2026-10-10T05:50Z is void. PR #282 landed as 765a6218, #279 is unblocked. next: implement

[loop:torbscript] claim released by session:loop-torbscript-20261010: no worker was started (sessions_spawn was not available in this tick), so the claim until 2026-10-10T05:50Z is void. PR #282 landed as 765a6218, #279 is unblocked. next: implement
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T06:10Z (a worker for this item is running since 00:09Z; the release in the previous comment was written by a tick that could not spawn)

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T06:10Z (a worker for this item is running since 00:09Z; the release in the previous comment was written by a tick that could not spawn)
argus closed this issue 2026-10-10 00:59:26 +00:00
Author
Collaborator

[loop:torbscript] implement: PR #292, CI success (all jobs green on the first run), worker usage unknown, next: waiting (review by the merge gate argus)

[loop:torbscript] implement: PR https://git.torb.dev/torbscript/language/pulls/292, CI success (all jobs green on the first run), worker usage unknown, next: waiting (review by the merge gate argus)
Author
Collaborator

[loop:torbscript] done: PR #292 merged by argus as e74cdc49 (PR CI green on head 0b6138e6; the PR has no After-merge steps). Issue closed by the PR. Merge-commit CI on main still running at 02:59 Berlin.

[loop:torbscript] done: PR #292 merged by argus as e74cdc49 (PR CI green on head 0b6138e6; the PR has no After-merge steps). Issue closed by the PR. Merge-commit CI on main still running at 02:59 Berlin.
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#279
No description provided.