Record that stage 1 shipped, and what publishing it cost

68d395854667 · AtlantisPleb · · parent 9624baa33625

Record that stage 1 shipped, and what publishing it cost

The audit described a plan; the plan's first stage is now a released command,
and a reader of the plan should not have to discover that elsewhere.

`openagents api` shipped as `@openagentsinc/cli@0.2.1`. The entry records the
command surface, the transport change the audit had missed -- it could not
express PATCH or PUT, so the issue-update route was unreachable whatever the
command layer did -- and the publish failure worth remembering: `npm publish`
from a pnpm workspace ships `catalog:` to the registry and installs nowhere.

It also restates what a passthrough cannot fix, because a faithful passthrough
exposes a wrong surface exactly as wrong: private issues still read 404, and
projectsV2 still answers for one hardcoded repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016o8HwTaqLKEWCHTjsjFtrB
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified docs/2026-08-21-cli-api-parity-audit.md

Diff

1 file changed, +34 -1

docs/2026-08-21-cli-api-parity-audit.md modified +34 -1

@@ -2,6 +2,7 @@

2 2
3 3
**Date:** 2026-08-21
4 4
**Commits measured:** `81e4c25eb5b5` (`openagents/main`, the forge) for the API; `5bd0061e4f6e` in the `openagents` monorepo for `packages/openagents-cli` (package version `0.1.7`)
5
**Status:** Stage 1 shipped on 2026-08-21 as `@openagentsinc/cli@0.2.1`; see section 5. Everything else below describes the surface as measured, unchanged.
5 6
**Question:** Does the CLI only cover repository upload? What of the Issues and Projects API does it reach? Should that coverage be generated from an OpenAPI document instead of hand-written? What is the fastest honest path to managing issues and projects from a terminal?
6 7
**Method:** direct reading of `lib/openagents_web/router.ex`, every controller it routes to under `/api/v3`, the contexts behind them (`lib/openagents/issues.ex`, `labels.ex`, `milestones.ex`, `projects.ex`, `repositories.ex`), the auth plugs, `lib/openagents_web/route_authority.ex`, `priv/api-contracts/repositories-v1.json` and its controller and test, and `docs/openagents-cli/`; plus direct reading of all 21 source files in the `openagents` monorepo at `packages/openagents-cli/src/` and its tests. The CLI lives in a different repository, so every CLI citation names it. Claims that neither repository can settle are in section 7 with the command that would settle them.
7 8

@@ -323,10 +324,42 @@ The order matters. The passthrough removes the urgency, the drift tests remove t

323 324
324 325
Each stage is independently shippable and independently useful. Sizes are rough and relative.
325 326
326
### Stage 1 — Ship `openagents api` (CLI repository only, small)
327
### Stage 1 — Ship `openagents api` (CLI repository only, small) — SHIPPED 2026-08-21
327 328
328 329
Add one leaf command taking a path, an optional `-X/--method`, repeated `-f key=value` body fields or `--input -` for raw JSON, and honoring the existing `--json` and profile flags. **Seam:** `packages/openagents-cli/src/cli.ts` plus a thin passthrough client in the `openagents` monorepo; no schema, no server change. **Size:** 150–250 lines with tests. **Effect:** every one of the 50 endpoints becomes reachable from a terminal, and every future endpoint arrives free. Also update `docs/openagents-cli/command-reference.md:171`, which currently lists a generic API command among the things the release does not provide, and fix the stale `repo delete` claim on the same line while you are in there.
329 330
331
**What shipped.** `eaa2aa1006` in the `openagents` monorepo, released as
332
`@openagentsinc/cli@0.2.1`. The command is `openagents api <path>`: a path
333
without a leading slash resolves under `/api/v3/`, an absolute path must start
334
with `/api/` and stay on the selected origin, `-X` covers GET, POST, PATCH,
335
PUT, and DELETE, `-f key=value` repeats into a JSON object, `--input <file|->`
336
takes a whole body from a file or standard input, `-H` repeats headers and
337
refuses to overwrite `authorization`, and `--profile`/`--api-url`/`--json`
338
behave as they do elsewhere. The body goes to stdout as JSON so it pipes into
339
`jq`; a non-2xx writes status, body, and request id to stderr and exits
340
non-zero, keeping stdout clean. 32 tests, and `ApiTransport` gained PATCH and
341
PUT, which it had never carried (`api-transport.ts:8`) — the audit missed that
342
the transport could not express the issue-update route at all.
343
344
**What it cost to publish, which is worth recording.** `0.2.0` went to npm with
345
`"effect": "catalog:"` in its dependencies and installed nowhere. The monorepo
346
is a pnpm workspace: pnpm resolves catalog references while packing, `npm
347
publish` does not, and the tarball reaches the registry looking healthy. npm
348
refuses unpublishing outside its window, so `0.2.0` could only be deprecated
349
and replaced. The rule now lives in that repository's `AGENTS.md` and
350
`docs/DEPLOYMENT.md`, along with the check that catches it in seconds:
351
install the published version from the registry rather than reading the
352
publish output.
353
354
**What Stage 1 does not fix**, both from section 3.2 and both server-side:
355
reading an issue back from a private repository still answers 404, because the
356
read pipeline discards the bearer token, so the passthrough can create an issue
357
it cannot then show you. And the `projectsV2` routes still resolve through
358
`Repositories.initial_repository!/0`, so `openagents api users/me/projectsV2`
359
returns real-looking JSON describing one hardcoded repository rather than
360
yours. A passthrough is faithful by design: it exposes those surfaces exactly
361
as they are, including where they are wrong.
362
330 363
### Stage 2 — Let an authenticated caller read their own private issues (this repository only, medium)
331 364
332 365
Move the issue, comment, label, milestone, and assignee read routes (`router.ex:235`–`:246`) from `pipe_through :api` into `pipe_through :optional_forge_api`, and change the resolution calls in `IssueController`, `CommentController`, `IssueLabelController`, `IssueAssigneeController`, `LabelController`, `MilestoneController`, and `AssigneeController` from `Repositories.get_public_by_path!/2` to `Repositories.get_visible_by_path!/3`, threading `conn.assigns.current_user` (which the optional plug sets to `nil` for anonymous callers). The visibility filters inside `Issues.get_issue_by_path!/3` (`issues.ex:159`), `Issues.get_comment_by_path!/3`, `Labels.get_label_by_path!/3`, and `Milestones.get_milestone_by_path!/3` need the same treatment. **Seam:** router pipelines, ten or so controller call sites, four context queries, and the `RouteAuthority` policy at `route_authority.ex:184`, whose `:public_read` classification for these paths becomes wrong. **Size:** medium; mechanical but wide, and every one of these controllers already has a test file to extend. **Effect:** additive and backward-compatible — anonymous callers see exactly what they see today; the change is that a bearer token now widens the result instead of being discarded. Without this, sections 3.1 ranks 1, 2, 8, and 9 only ever work on public repositories, so this gates most of the value of Stage 3.

This page updates live while a promote is in flight · changelog