Decide that gh is not a client this forge serves

1666117ff708 · AtlantisPleb · · parent adafa8308624

Decide that gh is not a client this forge serves

The API moved to /api/v1 and left one question open: gh reaches a
non-github.com host at a hardcoded /api/v3, so keeping that prefix
forever looked like the price of gh compatibility. Measured against gh
2.89.0 and the live application, the prefix buys nothing. gh issue list
sends GraphQL to /api/graphql and gh issue view probes GET /api/v3/meta;
neither is served, so no alias makes a ported command work. The one
surface the alias does serve, the gh api passthrough, already reaches
/api/v1 when it is given a full URL.

So the posture is GitHub-shaped, not gh-compatible: the openagents CLI
and any client you point at a base URL, and /api/v3 stays the dated
migration alias issue #216 deletes. ADR 0009 records the reasoning and
the measurements, the REST API page names the two clients that work and
the one that does not, and the episode 273 summary carries the
correction its "gh and octokit work unchanged" line needed.

Because the alias is temporary, nothing may come to depend on it.
FORGEAPI-002 states that every versioned route is declared at /api/v1
and every URL a response emits names /api/v1, and a proof reads lib/ for
the old prefix. The trace ingest route landed mid-rename returning a url
field naming /api/v3/traces/{id}, a link that would have gone dead the
day the alias did; that is fixed, and deleting the alias stays a
one-file change.

Closes #215.

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

Deploy story

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

pushed
by user · WAL seq 376 · 2026-08-25T14:27:13.374238Z

Changed files

  • modified INVARIANTS.md
  • added docs/decisions/0009-serve-a-github-shaped-api-not-a-gh-compatible-one.md
  • modified docs/episode-triage.md
  • modified docs/github-api-issues-projects-assessment.md
  • modified docs/taxonomy.md
  • modified lib/openagents_web/api_route_authority.ex
  • modified lib/openagents_web/controllers/trace_controller.ex
  • modified lib/openagents_web/plugs/api_v3_rewrite.ex
  • modified priv/docs/rest-api.md
  • added test/openagents_web/api_version_posture_test.exs
  • modified test/openagents_web/controllers/trace_controller_test.exs

Diff

11 files changed, +238 -26

INVARIANTS.md modified +39

@@ -5271,6 +5271,44 @@ Evidence: `lib/openagents_web/api_error.ex`,

5271 5271
`test/openagents_web/controllers/api_error_contract_test.exs`, and
5272 5272
`test/openagents_web/controllers/api_extension_controller_test.exs`.
5273 5273
5274
### FORGEAPI-002 — The path names this API's version, and `/api/v3` is a dated alias
5275
5276
Status: Current
5277
5278
The version segment in `/api/v1` names this API's own version. It does not
5279
name GitHub's. The API is GitHub-shaped — familiar paths and response shapes,
5280
so a client you already know is cheap to adapt — and it is not GitHub, so a
5281
version GitHub chose is the wrong number to serve under.
5282
5283
That distinction is load-bearing because one tool disagrees with it. GitHub's
5284
`gh` reaches any non-github.com host at a hardcoded `/api/v3` prefix, which
5285
reads as a reason to serve that prefix forever. It is not one. Measured
5286
against `gh` 2.89.0, the ported commands never reach REST: `gh issue list`
5287
sends GraphQL to `/api/graphql` and `gh issue view` probes `GET /api/v3/meta`,
5288
and this application serves neither, so the prefix buys no working command.
5289
The one surface it does serve, the `gh api` passthrough, already reaches
5290
`/api/v1` when it is given a full URL. `gh` is therefore not a supported
5291
client, and `/api/v3` is a migration alias for released clients with a
5292
deletion scheduled, not a compatibility promise. The reasoning and the
5293
measurements are
5294
`docs/decisions/0009-serve-a-github-shaped-api-not-a-gh-compatible-one.md`.
5295
5296
Because the alias is temporary, nothing may come to depend on it. Every
5297
versioned route is declared at `/api/v1`, and every URL a response emits names
5298
`/api/v1`, so a link this API hands a client survives the deletion.
5299
`OpenAgentsWeb.Plugs.ApiV3Rewrite` is the only file under `lib/` that names the
5300
old prefix at all, and a proof reads the tree for that, because the reasoning
5301
does not survive on care alone: the trace ingest route landed mid-rename and
5302
returned a `url` field naming `/api/v3/traces/{id}`, a link that would have
5303
gone dead the day the alias did. Deleting the alias stays what it should be, a
5304
one-file change with one test.
5305
5306
Evidence: `lib/openagents_web/plugs/api_v3_rewrite.ex`,
5307
`lib/openagents_web/router.ex`,
5308
`lib/openagents_web/controllers/trace_controller.ex`,
5309
`test/openagents_web/api_version_posture_test.exs`, and
5310
`test/openagents_web/plugs/api_v3_rewrite_test.exs`.
5311
5274 5312
## Executable proof index
5275 5313
5276 5314
This index is part of the ledger. Every `Current` invariant has at least one

@@ -5310,6 +5348,7 @@ contract; the invariant prose above defines the assertion, not the filename.

5310 5348
| NOTIFY-001 | `test/openagents/notifications_test.exs`, `test/openagents_web/live/notifications_live_test.exs` |
5311 5349
5312 5350
| FORGEAPI-001 | `test/openagents_web/controllers/api_error_contract_test.exs`, `test/openagents_web/controllers/api_extension_controller_test.exs`, `test/openagents_web/api_error_test.exs`, `test/openagents_web/controllers/issue_controller_test.exs` |
5351
| FORGEAPI-002 | `test/openagents_web/api_version_posture_test.exs`, `test/openagents_web/plugs/api_v3_rewrite_test.exs` |
5313 5352
| DATA-001 | `test/openagents/conversations_test.exs` |
5314 5353
| DATA-002 | `test/openagents/accounts_test.exs`, `test/openagents/conversations_test.exs` |
5315 5354
| DATA-003 | `test/openagents/conversations_test.exs` |
docs/decisions/0009-serve-a-github-shaped-api-not-a-gh-compatible-one.md added +76

@@ -0,0 +1,76 @@

1
# ADR 0009: Serve a GitHub-shaped API, not a `gh`-compatible one
2
3
Date: 2026-08-25
4
5
Status: Accepted
6
7
## Context
8
9
The API moved from `/api/v3` to `/api/v1` (issue #211). The version in the path
10
names this API's own version, and this API is at its first. One deploy serves
11
both paths: `OpenAgentsWeb.Plugs.ApiV3Rewrite` rewrites `/api/v3/*` to
12
`/api/v1/*` so that clients released against the old prefix keep working while
13
the fleet upgrades.
14
15
That alias raised a question the rename audit
16
(`docs/2026-08-24-api-v1-rename-audit.md`) left open, and issue #215 asks you
17
to settle: GitHub's own CLI hardcodes `https://HOST/api/v3/` for any host other
18
than github.com, with no way to override the path segment. If `gh` is a client
19
this forge wants, the alias can never be deleted.
20
21
The audit stopped at the path. Reading the path alone suggests that keeping
22
`/api/v3` buys `gh` compatibility for the price of one plug. Running `gh`
23
against the live application says otherwise. Every row below is `gh` 2.89.0
24
against `openagents.com` on 2026-08-25, captured with `GH_DEBUG=api`:
25
26
| Command | Request `gh` makes | Result |
27
| --- | --- | --- |
28
| `gh issue list -R …` | `POST /api/graphql` | `404` |
29
| `gh issue view 215 -R …` | `GET /api/v3/meta` | `406` |
30
| `gh api repos/OWNER/REPO/issues` | `GET /api/v3/repos/…/issues` | `200` |
31
| `gh api https://openagents.com/api/v1/repos/OWNER/REPO/issues` | `GET /api/v1/repos/…/issues` | `200` |
32
33
Three facts follow.
34
35
- The ported commands do not use REST. `gh issue list` sends a GraphQL query to
36
  `/api/graphql`, and `gh issue view` first probes `GET /api/v3/meta`. This
37
  application serves neither endpoint, so no alias makes those commands work.
38
- The one `gh` surface the alias keeps alive is `gh api`, the raw REST
39
  passthrough, which is a `curl` substitute rather than a reason to use `gh`.
40
- Even `gh api` does not need the alias. Given a full URL it calls `/api/v1`
41
  directly and answers `200`. Only a bare path picks up the `/api/v3` prefix.
42
43
## Decision
44
45
Serve a GitHub-shaped API, and do not claim `gh` compatibility.
46
47
- `gh` is not a supported client. The supported clients are the `openagents`
48
  CLI, which is first-class, and any GitHub-shaped client that takes an
49
  explicit base URL, such as Octokit configured with
50
  `baseUrl: "https://openagents.com/api/v1"`.
51
- `/api/v3` stays a dated migration alias for released clients, and it gets
52
  deleted on the schedule issue #216 describes. Nothing about `gh` makes it
53
  permanent.
54
- Every versioned route is declared at `/api/v1`, and every URL a response
55
  emits names `/api/v1`. `OpenAgentsWeb.Plugs.ApiV3Rewrite` is the only place
56
  in `lib/` that names the old prefix, so removing the alias stays a one-file
57
  change. `INVARIANTS.md` records this as FORGEAPI-002, and
58
  `test/openagents_web/api_version_posture_test.exs` proves it.
59
60
## Consequences
61
62
- Issue #216 proceeds. The gh-posture decision it waited on does not make the
63
  alias permanent.
64
- Documentation states the limit rather than implying drop-in tooling.
65
  `priv/docs/rest-api.md` names `gh` as unsupported and gives you the two
66
  clients that work. The episode 273 summary in `docs/episode-triage.md`
67
  carries a dated correction, because "so `gh` and octokit work unchanged"
68
  recorded an intent that the evidence does not support.
69
- A response that emits an `/api/v3` URL is now a build failure. The trace
70
  ingest route landed during the rename and returned a `url` field naming
71
  `/api/v3/traces/{id}`, a link that would have gone dead with the alias. That
72
  is fixed, and the proof stops the next one.
73
- If `gh` compatibility is ever worth having, the work is a GraphQL endpoint
74
  and `GET /api/v1/meta`, plus restoring the `/api/v3` prefix. That is a
75
  product decision about serving GraphQL, not a decision about a path segment,
76
  and it belongs in its own ADR.
docs/episode-triage.md modified +6 -3

@@ -1204,9 +1204,12 @@ references, "the last repo I ever make on GitHub." Devin scaffolds the Phoenix

1204 1204
app; build order: issues first so development organizes itself publicly, then
1205 1205
Projects V2 reads, then write endpoints, targeting GitHub API parity on the
1206 1206
subset actually used (84 endpoints in the March 2026 spec tied to
1207
issues/projects) so `gh` and octokit work unchanged. DaisyUI-vs-Basecoat debate
1208
resolved toward simplicity (later reversed by the staging incident recorded in
1209
AGENTS.md). Bounties, leaderboard, and in-forge chat teased.
1207
issues/projects) so `gh` and octokit work unchanged (half true, and corrected
1208
2026-08-25 by ADR 0009: the shape carries octokit given a base URL, but never
1209
carried `gh`, whose ported commands run on GraphQL rather than REST). The
1210
DaisyUI-vs-Basecoat debate resolved toward simplicity (later reversed by the
1211
staging incident recorded in AGENTS.md). Bounties, leaderboard, and in-forge
1212
chat teased.
1210 1213
1211 1214
#### 274 — Importing Repos
1212 1215
docs/github-api-issues-projects-assessment.md modified +3 -1

@@ -387,7 +387,9 @@ These are compatibility limits, not authorization fallbacks:

387 387
- Nonnumeric issue and milestone numbers can produce `500 Internal Server
388 388
  Error` instead of `404 Not Found`.
389 389
- Pagination and link headers are a bounded local contract, not complete
390
  Octokit or `gh` parity.
390
  Octokit parity. `gh` is not a supported client at any level: its ported
391
  commands run on GraphQL, which this application does not serve. See
392
  `docs/decisions/0009-serve-a-github-shaped-api-not-a-gh-compatible-one.md`.
391 393
- The error envelope is a superset of GitHub's: `message` and `errors` keep
392 394
  GitHub-compatible meaning, and `code`, `status`, `documentation_url`, and
393 395
  `request_id` are additions. GitHub's `errors` is an array of resource
docs/taxonomy.md modified +5 -1

@@ -90,7 +90,11 @@ application: issues, comments, labels, assignees, milestones, and Projects V2

90 90
(`projectsV2`). It mimics GitHub REST shapes as a compatibility aid and
91 91
implements only a subset of the real API. Authorization here comes from API
92 92
tokens with scopes such as `forge:write`; path similarity to GitHub proves
93
nothing about authorization.
93
nothing about authorization. **GitHub-shaped** is not **GitHub-compatible**:
94
the shape carries a client you configure with a base URL, and it does not
95
carry GitHub's `gh`, which is unsupported. `/api/v3` is a dated alias for
96
clients released before the rename, not a version this API claims. See
97
`docs/decisions/0009-serve-a-github-shaped-api-not-a-gh-compatible-one.md`.
94 98
95 99
**GitHub contexts (`OpenAgents.GitHub`, `github_oauth`)** — code that talks
96 100
*to* GitHub: OAuth identity, delegated repository access. Tokens are
lib/openagents_web/api_route_authority.ex modified +3 -2

@@ -417,8 +417,9 @@ defmodule OpenAgentsWeb.ApiRouteAuthority do

417 417
      "put /api/v1/repos/:owner/:repo/labels/:name" => {:required_bearer, :label, :envelope},
418 418
      "put /api/v1/repos/:owner/:repo/milestones/:milestone_number" =>
419 419
        {:required_bearer, :milestone, :envelope},
420
      # Landed at `/api/v3` while the rename was in flight. It moves with
421
      # everything else; the alias answers the prefix it was written against.
420
      # Landed under the old prefix while the rename was in flight. It moves
421
      # with everything else; the alias answers the prefix it was written
422
      # against until that alias goes away.
422 423
      "post /api/v1/traces" => {:required_bearer, :trace, :envelope}
423 424
    }
424 425
  end
lib/openagents_web/controllers/trace_controller.ex modified +2 -2

@@ -1,6 +1,6 @@

1 1
defmodule OpenAgentsWeb.TraceController do
2 2
  @moduledoc """
3
  Accept ATIF v1 trace uploads at `POST /api/v3/traces`.
3
  Accept ATIF v1 trace uploads at `POST /api/v1/traces`.
4 4
  """
5 5
6 6
  use OpenAgentsWeb, :controller

@@ -46,7 +46,7 @@ defmodule OpenAgentsWeb.TraceController do

46 46
  defp trace_view(trace) do
47 47
    %{
48 48
      "id" => trace.id,
49
      "url" => OpenAgentsWeb.Endpoint.url() <> "/api/v3/traces/" <> trace.id,
49
      "url" => OpenAgentsWeb.Endpoint.url() <> "/api/v1/traces/" <> trace.id,
50 50
      "digest" => trace.digest,
51 51
      "byte_size" => trace.byte_size,
52 52
      "visibility" => trace.visibility,
lib/openagents_web/plugs/api_v3_rewrite.ex modified +15 -3

@@ -2,9 +2,21 @@ defmodule OpenAgentsWeb.Plugs.ApiV3Rewrite do

2 2
  @moduledoc """
3 3
  Transparently rewrites `/api/v3/*` requests to `/api/v1/*`.
4 4
5
  During the `/api/v3` to `/api/v1` migration, legacy clients continue to call
6
  `/api/v3/*`. This plug modifies `conn.path_info` so the router routes the
7
  request to the `/api/v1` scopes without requiring an HTTP redirect.
5
  The API moved from `/api/v3` to `/api/v1` in one deploy, so clients released
6
  against the old prefix keep working while the fleet upgrades. This plug
7
  modifies `conn.path_info` before the router sees it, rather than redirecting,
8
  because the old clients `POST` and do not follow redirects reliably.
9
10
  The alias is dated, not permanent. It exists for released clients, not for
11
  `gh`: `gh` reaches a non-github.com host at `/api/v3`, but its ported
12
  commands run on GraphQL and `GET /api/v3/meta`, neither of which this
13
  application serves, so keeping the prefix would not make `gh` work. See
14
  `docs/decisions/0009-serve-a-github-shaped-api-not-a-gh-compatible-one.md`
15
  and `INVARIANTS.md`, FORGEAPI-002.
16
17
  This module is the only place in `lib/` that names the old prefix. Nothing
18
  routes at it and no response emits it, which is what makes deleting the alias
19
  a one-file change.
8 20
  """
9 21
  @behaviour Plug
10 22
priv/docs/rest-api.md modified +26 -1

@@ -337,13 +337,38 @@ The notes read is paginated and takes `page` and `kind`, where `kind` is `note`,

337 337
with write access receives `403 Forbidden`, and an activity entry is never
338 338
editable. See [Projects](/docs/projects).
339 339
340
## Choose a client
341
342
The paths are GitHub-shaped so that you can adapt a client you already know.
343
They are not a promise that GitHub's own tooling runs against this host.
344
345
- **The `openagents` CLI** is the first-class client. It finds the API origin,
346
  loads your stored credential, and returns JSON. See
347
  [Call the API with the CLI](/docs/cli-api).
348
- **A client that takes a base URL** works on shape alone. Point Octokit at
349
  `https://openagents.com/api/v1` and the issue, comment, label, assignee,
350
  milestone, and project reads and writes described on this page behave as the
351
  paths suggest, within the limits below.
352
- **GitHub's `gh` CLI is not supported.** Its ported commands do not use REST:
353
  `gh issue list` sends a GraphQL query to `/api/graphql`, and `gh issue view`
354
  first probes `GET /api/v3/meta`. OpenAgents serves neither, so those commands
355
  fail whatever the base path is. The `gh api` passthrough does work if you
356
  give it a full URL, as in `gh api
357
  https://openagents.com/api/v1/repos/OWNER/REPO/issues`, but `curl` and the
358
  `openagents` CLI do the same job without the confusion.
359
360
Requests to `/api/v3` still answer today. That prefix is a temporary alias for
361
clients released before the API moved to `/api/v1`, and it is scheduled for
362
removal. Send new work to `/api/v1`.
363
340 364
## Know the compatibility limits
341 365
342 366
- List responses use named envelopes such as `issues`, `comments`, `labels`,
343 367
  `milestones`, `assignees`, `projects`, `items`, and `fields`. GitHub commonly
344 368
  returns a bare array.
345 369
- Pagination, filters, link headers, and error envelopes form a bounded local
346
  contract. They do not provide complete Octokit or `gh` compatibility.
370
  contract. They do not provide complete Octokit compatibility, and `gh` is not
371
  a supported client at all.
347 372
- Ancillary issue-resource reads do not yet use the optional-bearer pipeline
348 373
  for private repositories.
349 374
- A nonnumeric issue or milestone number can produce `500 Internal Server
test/openagents_web/api_version_posture_test.exs added +50

@@ -0,0 +1,50 @@

1
defmodule OpenAgentsWeb.ApiVersionPostureTest do
2
  @moduledoc """
3
  Proves FORGEAPI-002: the version in the path is this API's own, and `/api/v3`
4
  is a dated alias nothing is allowed to depend on.
5
6
  The alias is one plug and one deletion away from gone. These assertions keep
7
  it that way, because the failure mode is quiet: a route or a rendered link
8
  written against the old prefix works perfectly until the day the alias is
9
  removed, and then it does not.
10
  """
11
12
  use ExUnit.Case, async: true
13
14
  @alias_plug "lib/openagents_web/plugs/api_v3_rewrite.ex"
15
  @legacy_prefix "api/v3"
16
17
  test "every versioned route is declared at /api/v1" do
18
    misversioned =
19
      OpenAgentsWeb.Router.__routes__()
20
      |> Enum.map(& &1.path)
21
      |> Enum.filter(&Regex.match?(~r{^/api/v\d}, &1))
22
      |> Enum.reject(&String.starts_with?(&1, "/api/v1"))
23
24
    assert misversioned == [], """
25
    These routes declare a version segment other than v1:
26
27
    #{Enum.map_join(misversioned, "\n", &"  #{&1}")}
28
29
    The path names this API's version. Old prefixes are served by
30
    OpenAgentsWeb.Plugs.ApiV3Rewrite, never declared in the router.
31
    """
32
  end
33
34
  test "the alias plug is the only file under lib/ that names the old prefix" do
35
    offenders =
36
      Path.wildcard("lib/**/*.ex")
37
      |> Enum.reject(&(&1 == @alias_plug))
38
      |> Enum.filter(&String.contains?(File.read!(&1), @legacy_prefix))
39
40
    assert offenders == [], """
41
    These files name `/#{@legacy_prefix}`:
42
43
    #{Enum.map_join(offenders, "\n", &"  #{&1}")}
44
45
    A route, a rendered `url` field, or a receipt hint that names the alias
46
    breaks when the alias is deleted. Name `/api/v1`, which is what the router
47
    serves and what the alias rewrites to.
48
    """
49
  end
50
end
test/openagents_web/controllers/trace_controller_test.exs modified +13 -13

@@ -1,6 +1,6 @@

1 1
defmodule OpenAgentsWeb.TraceControllerTest do
2 2
  @moduledoc """
3
  Accept ATIF v1 trace uploads at `POST /api/v3/traces`.
3
  Accept ATIF v1 trace uploads at `POST /api/v1/traces`.
4 4
  """
5 5
6 6
  use OpenAgentsWeb.ConnCase, async: false

@@ -8,12 +8,12 @@ defmodule OpenAgentsWeb.TraceControllerTest do

8 8
  alias OpenAgents.Repo
9 9
  alias OpenAgents.Traces.Trace
10 10
11
  describe "POST /api/v3/traces" do
11
  describe "POST /api/v1/traces" do
12 12
    test "returns the trace id and url for a valid ATIF v1.7 document", %{conn: conn} do
13 13
      body =
14 14
        conn
15 15
        |> put_chat_api_token("trace-valid")
16
        |> post(~p"/api/v3/traces", %{
16
        |> post(~p"/api/v1/traces", %{
17 17
          "schema_version" => "ATIF/1.7",
18 18
          "trace" => %{"events" => [%{"type" => "step"}]}
19 19
        })

@@ -21,7 +21,7 @@ defmodule OpenAgentsWeb.TraceControllerTest do

21 21
22 22
      assert %{"id" => id, "url" => url} = body
23 23
      assert is_binary(id)
24
      assert url =~ "/api/v3/traces/#{id}"
24
      assert url =~ "/api/v1/traces/#{id}"
25 25
      assert body["visibility"] == "dark"
26 26
      assert is_integer(body["byte_size"])
27 27
      assert is_binary(body["digest"])

@@ -39,13 +39,13 @@ defmodule OpenAgentsWeb.TraceControllerTest do

39 39
      first =
40 40
        conn
41 41
        |> put_chat_api_token("trace-dedup")
42
        |> post(~p"/api/v3/traces", document)
42
        |> post(~p"/api/v1/traces", document)
43 43
        |> json_response(201)
44 44
45 45
      second =
46 46
        conn
47 47
        |> put_chat_api_token("trace-dedup")
48
        |> post(~p"/api/v3/traces", document)
48
        |> post(~p"/api/v1/traces", document)
49 49
        |> json_response(200)
50 50
51 51
      assert second["id"] == first["id"]

@@ -60,7 +60,7 @@ defmodule OpenAgentsWeb.TraceControllerTest do

60 60
      body =
61 61
        conn
62 62
        |> put_chat_api_token("trace-oversize")
63
        |> post(~p"/api/v3/traces", %{
63
        |> post(~p"/api/v1/traces", %{
64 64
          "schema_version" => "ATIF/1.7",
65 65
          "data" => big
66 66
        })

@@ -71,7 +71,7 @@ defmodule OpenAgentsWeb.TraceControllerTest do

71 71
72 72
    test "rejects an unauthenticated call", %{conn: conn} do
73 73
      conn
74
      |> post(~p"/api/v3/traces", %{"schema_version" => "ATIF/1.7"})
74
      |> post(~p"/api/v1/traces", %{"schema_version" => "ATIF/1.7"})
75 75
      |> assert_api_error(401, "unauthenticated")
76 76
    end
77 77

@@ -79,7 +79,7 @@ defmodule OpenAgentsWeb.TraceControllerTest do

79 79
      conn =
80 80
        conn
81 81
        |> put_chat_api_token("trace-invalid")
82
        |> post(~p"/api/v3/traces", %{"schema_version" => "ATIF/2.0"})
82
        |> post(~p"/api/v1/traces", %{"schema_version" => "ATIF/2.0"})
83 83
84 84
      assert_api_error(conn, 422, "validation_failed",
85 85
        errors: %{"document" => ["The document is not a valid ATIF v1 object."]}

@@ -90,7 +90,7 @@ defmodule OpenAgentsWeb.TraceControllerTest do

90 90
      conn =
91 91
        conn
92 92
        |> put_chat_api_token("trace-no-version")
93
        |> post(~p"/api/v3/traces", %{"trace" => %{}})
93
        |> post(~p"/api/v1/traces", %{"trace" => %{}})
94 94
95 95
      assert_api_error(conn, 422, "validation_failed")
96 96
    end

@@ -99,7 +99,7 @@ defmodule OpenAgentsWeb.TraceControllerTest do

99 99
      body =
100 100
        conn
101 101
        |> put_chat_api_token("trace-visibility")
102
        |> post("/api/v3/traces?visibility=ledger", %{
102
        |> post("/api/v1/traces?visibility=ledger", %{
103 103
          "schema_version" => "ATIF/1.7",
104 104
          "trace" => %{}
105 105
        })

@@ -117,13 +117,13 @@ defmodule OpenAgentsWeb.TraceControllerTest do

117 117
      first =
118 118
        conn
119 119
        |> put_chat_api_token("trace-owner-one")
120
        |> post(~p"/api/v3/traces", document)
120
        |> post(~p"/api/v1/traces", document)
121 121
        |> json_response(201)
122 122
123 123
      second =
124 124
        conn
125 125
        |> put_chat_api_token("trace-owner-two")
126
        |> post(~p"/api/v3/traces", document)
126
        |> post(~p"/api/v1/traces", document)
127 127
        |> json_response(201)
128 128
129 129
      refute first["id"] == second["id"]

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