@oxy-hq/cli 0.6.0 → 0.7.0

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.
@@ -6,7 +6,7 @@
6
6
  # and its engineers publish from their machines with `oxyc login` and
7
7
  # `oxyc publish`; its own docs say "there is no GitHub Actions / CI". A repo
8
8
  # scaffolded from this template therefore behaves the same way unless somebody
9
- # asks for CI publishing, rather than arriving demanding a long-lived token the
9
+ # asks for CI publishing, rather than arriving demanding a credential setup the
10
10
  # rest of the org has already decided it does not want.
11
11
  #
12
12
  # In its own file for the same reason publish-env.sh is: a `run:` block cannot
@@ -14,14 +14,15 @@
14
14
  # until the first real push. This is the switch the whole opt-in rests on, so it
15
15
  # is the last thing that should be unexecuted.
16
16
  #
17
- # THE SWITCH IS EXPLICIT, AND IT IS NOT THE TOKEN. Skipping the publish whenever
18
- # no OXY_TOKEN reached the job would have been one line, and it would have
19
- # destroyed the property publish.yaml is built around: that a publish into a
20
- # misconfigured environment is a loud credential failure rather than a quiet
21
- # success. Inferred from the token, "we do not publish from CI" and "somebody
22
- # forgot production's OXY_TOKEN" are the same observation — and the second one
23
- # then ships nothing, silently, for as long as nobody looks. Two causes need two
24
- # states, which is what this file is.
17
+ # THE SWITCH IS EXPLICIT, AND IT IS NOT THE CREDENTIAL. Skipping the publish
18
+ # whenever the job could not authenticate would have been one line, and it would
19
+ # have destroyed the property publish.yaml is built around: that a publish into
20
+ # a misconfigured environment is a loud credential failure rather than a quiet
21
+ # success. Inferred from the credential, "we do not publish from CI" and
22
+ # "somebody forgot production's trust policy" (or its OXY_TOKEN, on the fallback
23
+ # path) are the same observation — and the second one then ships nothing,
24
+ # silently, for as long as nobody looks. Two causes need two states, which is
25
+ # what this file is.
25
26
  #
26
27
  # A VARIABLE rather than a secret, for two reasons that both matter. It is read
27
28
  # in the `build` job — the only job that runs when publishing is off, and so the
@@ -64,7 +65,7 @@ case "$value" in
64
65
  # misreading of a switch that costs something.
65
66
  ''|false|no|off|0)
66
67
  printf 'off\n'
67
- printf '::notice::CI publishing is off in this repo, which is the default. This run built the app bundles and published nothing. Publishing here is self-serve: from an app directory under apps/, run "oxyc login --env dev" once and then "oxyc publish --env dev" (or --env production). To publish from CI instead, add a repository VARIABLE named OXY_CI_PUBLISH with the value true — Settings, then Secrets and variables, then Actions, then the Variables tab — and give each GitHub environment its own OXY_TOKEN.\n' >&2
68
+ printf '::notice::CI publishing is off in this repo, which is the default. This run built the app bundles and published nothing. Publishing here is self-serve: from an app directory under apps/, run "oxyc login --env dev" once and then "oxyc publish --env dev" (or --env production). To publish from CI instead, add a repository VARIABLE named OXY_CI_PUBLISH with the value true — Settings, then Secrets and variables, then Actions, then the Variables tab — and register a trust policy for each GitHub environment, so the publish job needs no stored secret (or, as the fallback, give each environment its own OXY_TOKEN). The steps are under "Publishing custom apps" in README.md.\n' >&2
68
69
  ;;
69
70
  true|yes|on|1)
70
71
  printf 'on\n'
@@ -20,29 +20,40 @@
20
20
  # scaffolded repo should behave like that on day one rather than arriving with a
21
21
  # long-lived credential requirement nobody asked for.
22
22
  #
23
- # The switch is EXPLICIT rather than inferred from the token, and that is the
24
- # design rather than a preference. Skipping whenever no OXY_TOKEN reached the
25
- # job would be one line, and it would make "this repo does not publish from CI"
26
- # indistinguishable from "somebody forgot production's token" — the second of
27
- # which would then ship nothing, quietly, for as long as nobody looked. With the
28
- # switch ON a missing token is the loud failure it has always been, which is the
29
- # property the separate states exist to keep. The reasoning, and why it is a
30
- # variable and not a secret, is in `.github/scripts/ci-publish.sh`.
23
+ # The switch is EXPLICIT rather than inferred from a credential, and that is
24
+ # the design rather than a preference. Skipping whenever the job could not
25
+ # authenticate would be one line, and it would make "this repo does not publish
26
+ # from CI" indistinguishable from "somebody forgot production's trust policy" —
27
+ # the second of which would then ship nothing, quietly, for as long as nobody
28
+ # looked. With the switch ON a run that cannot authenticate is a loud failure,
29
+ # which is the property the separate states exist to keep. The reasoning, and
30
+ # why it is a variable and not a secret, is in `.github/scripts/ci-publish.sh`.
31
31
  #
32
32
  # Deliberately NOT modelled on pokehouse-oxy's `franchise-report-app.yml`
33
33
  # (Docker -> ECR -> ArgoCD). That is the only real, green CI on disk in this
34
34
  # org, which is exactly what makes it tempting; it is a legacy path for the
35
35
  # one app still on Next.js and has nothing to do with `oxyc publish`.
36
36
  #
37
- # Auth is a static `OXY_TOKEN` secret, not GitHub OIDC trusted publishing.
38
- # Both appear in this org's examples and a workflow that MIXES them 403s.
39
- # OIDC also needs a one-time publisher-registration POST that nothing
40
- # automates — a manual onboarding step this scaffold exists to remove.
37
+ # AUTH IS GITHUB OIDC BY DEFAULT: NO STORED SECRET. The publish job holds
38
+ # `id-token: write`, and `oxyc publish` trades the job's OIDC token for a
39
+ # fifteen-minute Oxy token acting as a service account, which it revokes when
40
+ # the command ends. What makes the deployment honour that is a TRUST POLICY on
41
+ # the service account, naming this repository, this workflow file
42
+ # (`.github/workflows/publish.yaml`) and the job's `environment:` — one policy
43
+ # per environment, each granted publishing the apps here. An org admin registers
44
+ # it once, in the web app under Organization settings -> Service accounts ->
45
+ # Trusted access; "Publishing custom apps" in README.md has the steps.
46
+ #
47
+ # A STATIC `OXY_TOKEN` SECRET IS THE FALLBACK, and it wins when it is set: a
48
+ # repo that already publishes with one keeps doing exactly that, and a
49
+ # deployment without trusted access has no other way. It is one or the other
50
+ # per run, never a mix — with `OXY_TOKEN` set, no OIDC token is requested.
41
51
  #
42
52
  # TWO JOBS, and the split is the security boundary, not a style choice.
43
53
  # `build` runs the bundles' own postinstall and build scripts — arbitrary
44
54
  # third-party code out of node_modules — and holds no publish credential.
45
- # `publish` holds `OXY_TOKEN`, downloads what `build` produced, and runs no
55
+ # `publish` holds the credential — the `id-token` permission, or the fallback
56
+ # `OXY_TOKEN` — downloads what `build` produced, and runs no
46
57
  # package script at all. That includes Oxy Functions: `build` bundles them with
47
58
  # `oxyc publish --build-only`, and `publish` runs `oxyc publish --prebuilt`,
48
59
  # which checks they arrived rather than running esbuild — so the credential and
@@ -60,9 +71,10 @@
60
71
  # fails rather than falling through to the more dangerous of the two.
61
72
  #
62
73
  # The same resolved name does two jobs: it is the `--env` passed to the CLI AND
63
- # the GitHub `environment:` the publish job runs in, which is where that env's
64
- # own `OXY_TOKEN` lives. So the credential and the destination cannot disagree
65
- # — a production publish can only ever be made with production's token. It is
74
+ # the GitHub `environment:` the publish job runs in — the environment a trust
75
+ # policy is bound to, and where the fallback `OXY_TOKEN` for that env lives. So
76
+ # the credential and the destination cannot disagree — a production publish can
77
+ # only ever be made by a run production's policy (or token) covers. It is
66
78
  # resolved ONCE, by `.github/scripts/publish-env.sh` in the `build` job, and
67
79
  # read twice; a job's `environment:` can read `needs`, so that is the only
68
80
  # place a single value can reach both consumers.
@@ -141,7 +153,14 @@ env:
141
153
  # validate.yaml's `OXY_VERSION`: that one is the Rust `oxy` that validates the
142
154
  # workspace, a different tool on a different release line. `--build-only` and
143
155
  # `--prebuilt`, which the two jobs below depend on, arrived in 0.4.0.
144
- OXYC_VERSION: "0.4.0"
156
+ #
157
+ # THE PIN DECIDES WHICH OIDC REGISTRATION THE PUBLISH JOB CAN USE. Through
158
+ # 0.6.0, `oxyc publish` knows one exchange: the app's own registered
159
+ # publisher (`/api/customer-apps/<app-id>/publishers`). Trust policies on a
160
+ # service account are honoured from 0.7.0, which tries them first and falls
161
+ # back to the publisher. A pin older than that still publishes, through the
162
+ # publisher alone.
163
+ OXYC_VERSION: "0.7.0"
145
164
 
146
165
  jobs:
147
166
  build:
@@ -404,15 +423,17 @@ jobs:
404
423
  # The gate, and the credential boundary. `dev` on a push, `production` on
405
424
  # a manual dispatch — the SAME string this job passes to `oxyc publish
406
425
  # --env` below, resolved once in `build`, so the destination this job
407
- # publishes to and the environment it draws its secrets from are the same
408
- # thing by construction. Each environment holds its own `OXY_TOKEN`, and a
409
- # missing one is caught explicitly below rather than sent as an empty
410
- # bearer.
426
+ # publishes to and the environment it authenticates as are the same thing
427
+ # by construction. The environment's name is a claim in the job's OIDC
428
+ # token, so a trust policy registered for `production` cannot be satisfied
429
+ # by a run in `dev`; and where the fallback is in use, each environment
430
+ # holds its own `OXY_TOKEN`.
411
431
  #
412
432
  # The one hole, and it is a human's to close rather than this file's: a
413
433
  # repository-level `OXY_TOKEN`, if one exists, is still reachable here —
414
434
  # environment secrets take PRECEDENCE over repository secrets, they do not
415
- # exclude them. See the note on `OXY_TOKEN` in the publish step.
435
+ # exclude them — and a token that is set wins over OIDC. See the note on
436
+ # `OXY_TOKEN` in the publish step.
416
437
  #
417
438
  # Referencing an environment that does not exist CREATES it on the first
418
439
  # run ("Running a workflow that references an environment that does not
@@ -426,6 +447,17 @@ jobs:
426
447
  # need GitHub Team or Pro; oxy-hq is on Team.
427
448
  environment: ${{ needs.build.outputs.publish-env }}
428
449
 
450
+ # `id-token: write` lets this job ask GitHub for a signed statement of what
451
+ # it is — repository, workflow file, environment — which is the whole
452
+ # credential on the default path. It grants nothing in the repository, and
453
+ # it is job-level: every step below can use it, which is why this job runs
454
+ # no package script. `contents: read` is restated because a job-level
455
+ # `permissions` block REPLACES the workflow-level one rather than adding to
456
+ # it. The `build` job is deliberately not given the token.
457
+ permissions:
458
+ id-token: write
459
+ contents: read
460
+
429
461
  steps:
430
462
  - uses: actions/checkout@v4
431
463
  with:
@@ -434,8 +466,8 @@ jobs:
434
466
  # bundle built from one commit while claiming another is precisely
435
467
  # the confusion the build id exists to prevent.
436
468
  ref: ${{ needs.build.outputs.sha }}
437
- # This job holds OXY_TOKEN. It has no reason to also be holding a
438
- # repository credential in .git/config.
469
+ # This job holds the publish credential. It has no reason to also be
470
+ # holding a repository credential in .git/config.
439
471
  persist-credentials: false
440
472
 
441
473
  - uses: actions/download-artifact@v4
@@ -492,6 +524,14 @@ jobs:
492
524
 
493
525
  - name: Publish each bundle
494
526
  env:
527
+ # THE FALLBACK CREDENTIAL, empty unless somebody set the secret. With
528
+ # it empty — the default — `oxyc publish` exchanges this job's OIDC
529
+ # token instead and nothing is stored anywhere. With it set, it WINS:
530
+ # no OIDC token is requested, and the run authenticates exactly as it
531
+ # did before OIDC was the default. Any kind of Oxy token works here —
532
+ # a service account token (`oxy_sat_…`), a personal access token
533
+ # (`oxy_pat_…`), or a workspace API key from before either existed.
534
+ #
495
535
  # Step-scoped as well as job-isolated. The job split is what keeps
496
536
  # this token away from the bundles' own build scripts — those ran
497
537
  # in `build`, on a different runner, and nothing in THIS job runs a
@@ -509,15 +549,27 @@ jobs:
509
549
  # precedence, not exclusion: an environment secret OVERRIDES a
510
550
  # repository one of the same name, it does not suppress it. So a
511
551
  # repo still carrying an old repository-level `OXY_TOKEN` serves
512
- # BOTH environments from it wherever the environment has none — the
513
- # guard below never fires, and each publish looks perfectly healthy
514
- # while authenticating with a token nobody scoped to it.
552
+ # BOTH environments from it wherever the environment has none — OIDC
553
+ # is never tried, and each publish looks perfectly healthy while
554
+ # authenticating with a token nobody scoped to it.
515
555
  #
516
556
  # Nothing here can catch that: GitHub does not tell a job which
517
557
  # level a secret came from, so from inside this step the two cases
518
558
  # are identical. It has to be a human, once: DELETE any
519
- # repository-level `OXY_TOKEN` after adding the environment ones.
559
+ # repository-level `OXY_TOKEN` — after registering the trust policies
560
+ # (or adding the environment-level tokens) that replace it. The step
561
+ # below prints which of the two credentials it used, every run.
520
562
  OXY_TOKEN: ${{ secrets.OXY_TOKEN }}
563
+ # The ID of the service account the OIDC exchange acts as — from the
564
+ # account's page under Organization settings → API access. REQUIRED
565
+ # for a trust policy to be used: the deployment matches this run
566
+ # against that one account's policies and never picks an account on
567
+ # a run's behalf. Unset, the publish goes through each app's
568
+ # registered publisher instead. The ID, never `<org-slug>/<name>`: a
569
+ # name can be taken over by another organization, an ID cannot.
570
+ # Unused on the `OXY_TOKEN` path. An ENVIRONMENT variable where
571
+ # `dev` and `production` use different accounts.
572
+ OXY_SERVICE_ACCOUNT: ${{ vars.OXY_SERVICE_ACCOUNT }}
521
573
  # `dev` or `production`, resolved once in the build job. Passed to
522
574
  # `oxyc publish --env` below; the identical string selected the
523
575
  # environment this job is running in.
@@ -552,8 +604,28 @@ jobs:
552
604
  exit 1
553
605
  fi
554
606
 
555
- if [ -z "${OXY_TOKEN:-}" ]; then
556
- echo "::error::no OXY_TOKEN reached this job, so there is nothing to authenticate with. Neither the '$PUBLISH_ENV' environment nor the repository holds one — an environment secret takes precedence over a repository secret of the same name, so this fires only when both are absent. Mint a publish-scoped API key in oxy and add it under Settings -> Environments -> $PUBLISH_ENV -> Environment secrets, NOT at repository level: a repository-level token would serve both environments and this check could never tell you." >&2
607
+ # Which credential this run uses, said out loud on every run — the
608
+ # two paths fail differently, and "which one was it" should never
609
+ # need working out from a stack of 403s.
610
+ if [ -n "${OXY_TOKEN:-}" ]; then
611
+ echo "authenticating with the OXY_TOKEN secret (the fallback path). To publish with no stored secret instead, register a trust policy for the '$PUBLISH_ENV' environment and delete the secret — see 'Publishing custom apps' in README.md."
612
+ # A service account is chosen by the OIDC exchange only; leaving
613
+ # the variable set would be a setting that silently does nothing.
614
+ unset OXY_SERVICE_ACCOUNT
615
+ elif [ -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
616
+ echo "authenticating with this job's GitHub OIDC token — no stored secret. oxyc exchanges it for a fifteen-minute token on each publish and revokes that token when the command ends."
617
+ # An unset secret arrives as an EMPTY variable, not a missing one.
618
+ # oxyc treats the two alike; unsetting it says so here too.
619
+ unset OXY_TOKEN
620
+ if [ -z "${OXY_SERVICE_ACCOUNT:-}" ]; then
621
+ unset OXY_SERVICE_ACCOUNT
622
+ echo "no OXY_SERVICE_ACCOUNT variable on the '$PUBLISH_ENV' environment: each app publishes through its registered publisher. To use a trust policy, set the variable to the service account's ID."
623
+ fi
624
+ else
625
+ # Reachable only if this job lost `id-token: write` — an edit to
626
+ # the `permissions` block above, or an org policy that withholds
627
+ # it. Fail here, by name, rather than as oxyc's "not authenticated".
628
+ echo "::error::this job has nothing to authenticate with: GitHub issued it no OIDC token and no OXY_TOKEN secret is set. The publish job needs 'permissions: id-token: write' (see the job's permissions block), and a trust policy registered for the '$PUBLISH_ENV' environment — or, as the fallback, an OXY_TOKEN secret under Settings -> Environments -> $PUBLISH_ENV -> Environment secrets, NOT at repository level." >&2
557
629
  exit 1
558
630
  fi
559
631
 
@@ -201,11 +201,11 @@ stops publishing with nobody told, and guessed as on publishes when nobody
201
201
  asked, so it is not guessed.
202
202
 
203
203
  Note what the switch does **not** do. It decides whether the publish job runs;
204
- it never softens what happens once it has. With CI publishing on, a missing
205
- `OXY_TOKEN` is a **hard failure** exactly as it always was. That is the reason
206
- there is a switch at all rather than the obvious shortcut of skipping whenever
207
- no token turns up: inferred from the token, "this repo does not publish from
208
- CI" and "somebody forgot production's token" are the same observation — and
204
+ it never softens what happens once it has. With CI publishing on, a run that
205
+ cannot authenticate is a **hard failure**. That is the reason there is a switch
206
+ at all rather than the obvious shortcut of skipping whenever no credential
207
+ turns up: inferred from the credential, "this repo does not publish from CI"
208
+ and "somebody forgot production's trust policy" are the same observation — and
209
209
  the second one ships nothing, quietly, for as long as nobody looks.
210
210
 
211
211
  Everything from here to the end of this section describes a repo that has
@@ -241,9 +241,10 @@ is no code path that can fall back to a default.
241
241
 
242
242
  There is deliberately **no environment picker** on the manual run. An input
243
243
  would need a default, which is the thing being removed; and the same resolved
244
- name also selects the GitHub environment whose `OXY_TOKEN` the job publishes
245
- with, so leaving it to a form field would mean the credential and the
246
- destination agree only by an operator's care. To publish a dev draft by hand,
244
+ name also selects the GitHub environment the job runs in — which is what its
245
+ credential is bound to — so leaving it to a form field would mean the
246
+ credential and the destination agree only by an operator's care. To publish a
247
+ dev draft by hand,
247
248
  re-run the push-triggered run from the Actions tab — a re-run keeps the
248
249
  original event, so it still resolves to `dev`.
249
250
 
@@ -251,10 +252,10 @@ It is **two jobs**, and the split is the security boundary rather than a
251
252
  tidiness one. `build` runs `pnpm install` and `pnpm -r build` — which is to
252
253
  say it executes the postinstall and build scripts of every dependency your
253
254
  apps pull in — and holds no publish credential at all. It hands the built
254
- output to `publish` as a workflow artifact. `publish` holds `OXY_TOKEN`, and
255
- runs no package script: `oxyc publish --dir` uploads a pre-built directory
255
+ output to `publish` as a workflow artifact. `publish` holds the credential,
256
+ and runs no package script: `oxyc publish --dir` uploads a pre-built directory
256
257
  as-is instead of building anything. So a compromised dependency in an app's
257
- tree never runs on a runner where the token exists.
258
+ tree never runs on a runner where the credential exists.
258
259
 
259
260
  **On a fresh repo it does nothing at all, and that is the intended
260
261
  behaviour.** The scaffold ships no apps and no `pnpm-lock.yaml` — there is
@@ -264,32 +265,90 @@ the run goes **green**. The install is skipped along with the publish
264
265
  deliberately: `pnpm install --frozen-lockfile` fails without a lockfile, and
265
266
  a repo whose CI is red the day it is created is a repo where nobody reads CI.
266
267
 
267
- Before it can publish anything, each of the two **GitHub environments** —
268
- `dev` and `production` — needs its own **`OXY_TOKEN`** (Settings →
269
- Environments → *environment* → Environment secrets), a publish-scoped API key
270
- minted in oxy against that workspace. This is the credential the self-serve
271
- model does without, and it is worth knowing what it is before you create one:
272
- a personal API key records the project it was minted against, but that project
273
- never reaches validation — a leaked key carries the minter's **user-level**
274
- access, not "this app's project only".
275
-
276
- **A missing one fails the run, loudly, and that is the intended behaviour.**
277
- It is also why CI publishing is a switch rather than something inferred from
278
- the token's presence: with the switch on, no token means a red run naming the
279
- environment it was looked for in, never a green run that published nothing.
268
+ #### How CI authenticates: no stored secret
269
+
270
+ **By default the publish job holds no secret at all.** It is granted
271
+ `id-token: write`, which lets it ask GitHub for a signed statement of what it
272
+ is — this repository, this workflow file, the environment the job runs in.
273
+ `oxyc publish` hands that statement to oxy and gets back a token that lasts
274
+ fifteen minutes, acts as a **service account** of your organization, and is
275
+ revoked when the command ends. There is nothing to rotate and nothing to leak
276
+ from Settings.
277
+
278
+ What makes oxy honour the statement is a **trust policy** on the service
279
+ account, and it has to exist before the first publish. An org admin registers
280
+ one **per environment**, once:
281
+
282
+ 1. In the web app: **Organization settings → API access → Service accounts**.
283
+ Create a service account — `deployer` is the conventional name — with the
284
+ role *member*.
285
+ 2. Open it and, under **Trusted access**, add a policy for each of `dev` and
286
+ `production`:
287
+
288
+ | Field | Value |
289
+ | --- | --- |
290
+ | Repository | this repo, as `<owner>/<repo>` |
291
+ | Workflow | `.github/workflows/publish.yaml` |
292
+ | Environment | `dev` (and a second policy for `production`) |
293
+ | Grant | publishing the apps under `apps/` — and nothing else |
294
+
295
+ The environment is part of the match, so the `production` policy cannot be
296
+ satisfied by a push-triggered run in `dev`: the credential and the destination
297
+ agree by construction, exactly as they did when each environment held its own
298
+ token. A run no policy matches fails with the repository, workflow and ref it
299
+ presented, which is usually enough to see which field is off.
300
+
301
+ **Name the account the workflow acts as, by its ID**, in an
302
+ `OXY_SERVICE_ACCOUNT` **variable** on the environment. The ID is on the
303
+ account's page (Organization settings → API access → Service accounts → the
304
+ account). It is required for a trust policy to be used at all: the deployment
305
+ matches a run against the policies of the one account its workflow names, and
306
+ never picks one on a run's behalf — anyone can register a policy that names a
307
+ repository. With the variable unset, the publish goes through each app's
308
+ *registered publisher* instead, as it did before trust policies existed.
309
+
310
+ The ID, never `<org-slug>/<name>`: a slug is free for anyone once its org
311
+ renames or is deleted, so a name can be taken over and an ID cannot. A name in
312
+ the variable is refused (`service_account_required`).
313
+
314
+ > Trust policies need a recent enough CLI in CI. The workflow pins the one it
315
+ > installs as `OXYC_VERSION`; the comment beside the pin says which release
316
+ > first honours them. An older pin still publishes with no stored secret, but
317
+ > only through an app's *registered publisher* — the earlier, per-app
318
+ > registration (`/api/customer-apps/<app-id>/publishers`), which Oxy staff set
319
+ > up.
320
+
321
+ #### The fallback: an `OXY_TOKEN` secret
322
+
323
+ A static token still works, and **when it is set it wins** — the job then
324
+ requests no OIDC token and authenticates exactly as this workflow always did.
325
+ Use it where the deployment has no trusted access yet, or where a repository
326
+ cannot be given a trust policy. Each of the two **GitHub environments** — `dev`
327
+ and `production` — holds its own **`OXY_TOKEN`** (Settings → Environments →
328
+ *environment* → Environment secrets). Prefer a **service account token**
329
+ (Organization settings → API access → Service accounts → *account* → Tokens),
330
+ granted only what publishing needs; a personal access token or an older
331
+ workspace API key also works, and carries its owner's access with it — a
332
+ leaked one is that person, not "this app only".
333
+
334
+ Every run prints which of the two it used. **A run with neither fails,
335
+ loudly, and that is the intended behaviour.** It is also why CI publishing is
336
+ a switch rather than something inferred from a credential's presence: with the
337
+ switch on, a job that cannot authenticate is a red run that says why, never a
338
+ green run that published nothing.
280
339
 
281
340
  **And any repository-level `OXY_TOKEN` must be DELETED, not left in place.**
282
341
  GitHub's order is environment → repository → organisation, and that is
283
342
  *precedence, not exclusion*: an environment secret overrides a repository one
284
343
  of the same name, it does not suppress it. A repo still carrying an old
285
344
  repository-level token therefore serves **both** environments from it wherever
286
- the environment has none — every run looks healthy, the missing-token check
287
- never fires, and production ships under whatever that token was scoped to.
345
+ the environment has none — every run looks healthy, OIDC is never tried, and
346
+ production ships under whatever that token was scoped to.
288
347
 
289
348
  Nothing in CI can catch it. GitHub does not tell a job which level a secret
290
349
  came from, so the two cases are identical from inside the run. It is a
291
350
  one-time human check, and it is the single thing to get right when moving an
292
- existing repo onto environments:
351
+ existing repo onto trust policies or onto environments:
293
352
 
294
353
  ```bash
295
354
  gh secret list --repo <owner>/<repo> # repository level
@@ -311,10 +370,13 @@ So adding the first app is two things — three, if this repo publishes from CI:
311
370
  lockfile it writes. Only the lockfile: `node_modules/` and every app's
312
371
  build output are covered by `.gitignore`, so `git add -A` after an install
313
372
  stages the one file CI needs and none of the ones it does not;
314
- 3. **only with `OXY_CI_PUBLISH` set** — an `OXY_TOKEN` in each environment.
373
+ 3. **only with `OXY_CI_PUBLISH` set** — a trust policy for each environment
374
+ that grants publishing the app (or, as the fallback, an `OXY_TOKEN` in each
375
+ environment).
315
376
 
316
377
  Miss one and the workflow fails loudly and names it: the discovery step
317
- points at the manifest, or the missing lockfile, or the missing secret. It
378
+ points at the manifest, or the missing lockfile; the publish step says no
379
+ trust policy matched the run, or that it had nothing to authenticate with. It
318
380
  does not guess and it does not half-publish. (The first two are needed even
319
381
  with CI publishing off: the `build` job installs and builds either way.)
320
382
 
@@ -397,8 +459,10 @@ someone points each at the branch:
397
459
 
398
460
  Promoting the production semantic model is a deliberate, human step: open the
399
461
  production workspace in Oxygen and pull the latest commit. **CI does not do
400
- it**, and holds no credential that could — the `OXY_TOKEN` in each environment
401
- is publish-scoped and cannot compile or promote a workspace at all. So a merge
462
+ it**, and holds no credential that could — the trust policy behind each
463
+ environment grants publishing apps and nothing else, so the token the job is
464
+ handed cannot compile or promote a workspace at all. (Keep it that way on the
465
+ fallback path: an `OXY_TOKEN` should be a token that can only publish.) So a merge
402
466
  to `main` never changes what production answers until someone decides it
403
467
  should.
404
468
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-D5FFpqAw.mjs","names":[],"sources":["../src/commands/mcp.ts"],"sourcesContent":["/**\n * `oxyc mcp` — the Oxy API as MCP tools, over stdio.\n *\n * NOT THE SAME THING AS `oxy mcp`. That one is workspace tooling: it takes a\n * local checkout and exposes the semantic model, automations and `.sql` files\n * in it. This one takes a TOKEN and exposes the deployment's HTTP API — the\n * same surface `oxyc api` reaches, for an agent that would rather call a tool\n * than shell out. Different input, different audience, no overlap.\n *\n * FOUR GENERIC TOOLS FOR THE WHOLE API — the design decision that makes most\n * of it affordable.\n *\n * The obvious shape is one tool per endpoint, and it is the wrong one: the API\n * has ~670 routes, an agent runtime ships every tool's JSON schema in every\n * request, and that is tens of kilobytes of context spent on each turn before\n * a single question is asked. It would also go stale on every deploy, since\n * the tool list would be baked into this package rather than read from the\n * deployment.\n *\n * So discovery stays a QUESTION the agent asks (`oxy_routes`, `oxy_schema`)\n * rather than a payload it carries, exactly as the CLI does it, for\n * everything a generic request can do safely.\n *\n * TWO NAMED EXCEPTIONS: `SANDBOX_TOOLS` and `PREVIEW_TOOLS` below. The sandbox\n * loop and the workspace-previews loop are meant to run UNSUPERVISED — create\n * a sandbox, publish into it, call a function, tear it down — and that needs\n * per-operation validation (the `dev-<handle>` grammar, a confirm gate before\n * a delete, a hard refusal of `--app-env` outside `dev-<handle>` on the\n * publish tool) that `oxy_request` has no way to carry. Each of those tools\n * calls its CLI verb's own request-building function (`env.ts`, `fn.ts`,\n * `checks.ts`, `preview.ts`, `preview-runs.ts`, …), so there is exactly one\n * implementation of each request, and a tool's result is the same JSON\n * document that verb's `--json` prints. `mcp.test.ts` pins the full tool list\n * and a raised (and justified) per-turn schema budget.\n *\n * Auth, target resolution and placeholder substitution are the CLI's — the\n * same `Context`, so a token cached by `oxyc login` works\n * here with no separate setup, and `{org}` / `{workspace}` resolve the same\n * way.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { loadCatalog, loadOpenApi, searchRoutes } from \"../api/catalog.js\";\nimport { paramsToQuery, parseFields } from \"../api/fields.js\";\nimport { runJq } from \"../api/output.js\";\nimport { isExternalSurface, normalizePath, substitutePlaceholders } from \"../api/paths.js\";\nimport { parseJson, request } from \"../api/request.js\";\nimport { requireSandboxName } from \"../apps/environment.js\";\nimport { type Context, createContext } from \"../context/resolve.js\";\nimport {\n CliError,\n ExitCode,\n type ExitCodeValue,\n exitCodeName,\n usageError\n} from \"../util/errors.js\";\nimport { runChecksCore } from \"./checks.js\";\nimport { comparablePath } from \"./discover.js\";\nimport { envCreate, envDelete, envList, envShow } from \"./env.js\";\nimport { fnCall } from \"./fn.js\";\nimport { invocationsHeld, invocationsList } from \"./invocations.js\";\nimport { fetchLogs } from \"./logs.js\";\nimport {\n previewChecks,\n previewCreate,\n previewDelete,\n previewFailed,\n previewList,\n previewShow\n} from \"./preview.js\";\nimport {\n parseVariables,\n previewRunGet,\n previewRunsList,\n previewSubmitRun,\n requireRunKind,\n runFailed\n} from \"./preview-runs.js\";\nimport { publish } from \"./publish.js\";\n\n/**\n * Package version, reported in the MCP handshake.\n *\n * Read from `package.json` rather than restated, so a release bump cannot\n * leave the server announcing a version nobody shipped.\n */\nconst VERSION: string = (() => {\n try {\n const here = dirname(fileURLToPath(import.meta.url));\n for (const candidate of [\n resolve(here, \"..\", \"package.json\"),\n resolve(here, \"..\", \"..\", \"package.json\")\n ]) {\n if (existsSync(candidate)) {\n return (\n (JSON.parse(readFileSync(candidate, \"utf8\")) as { version?: string }).version ?? \"0.0.0\"\n );\n }\n }\n } catch {\n // A packaging shape we did not anticipate. The handshake needs a string,\n // not an accurate one.\n }\n return \"0.0.0\";\n})();\n\n/**\n * A tool result. MCP wants content blocks; everything here is text, because\n * the caller is a language model and the payloads are JSON or markdown.\n */\nfunction text(body: string, isError = false) {\n return { content: [{ type: \"text\" as const, text: body }], isError };\n}\n\n/**\n * A tool result carrying one JSON document — the same shape the matching\n * verb's `--json` prints, so a tool's result and the CLI's output never\n * drift apart. On failure the exit-code class is appended, same spelling as\n * the generic catch-all below (`[exit N NAME]`), so an agent branches on it\n * the way it would on `oxyc`'s own exit code.\n */\nfunction jsonResult(data: unknown, opts: { isError?: boolean; code?: ExitCodeValue } = {}) {\n const body = JSON.stringify(data);\n if (!opts.isError) return text(body);\n const code = opts.code ?? ExitCode.FAILURE;\n return text(`${body}\\n\\n[exit ${code} ${exitCodeName(code)}]`, true);\n}\n\nconst TOOLS = [\n {\n name: \"oxy_routes\",\n description:\n \"List the API endpoints this Oxy deployment mounts, with what each one does. \" +\n \"ALWAYS call this before oxy_request rather than guessing a path. \" +\n \"Pass a filter (matched against method, path, surface and description) to narrow it — \" +\n \"unfiltered is ~670 routes. Above 60 matches the descriptions are dropped and only \" +\n \"method, path and credential come back, so narrow enough to stay under that when you \" +\n \"need to know what an endpoint does; past ~400 it is refused outright.\",\n inputSchema: {\n type: \"object\",\n properties: {\n filter: {\n type: \"string\",\n description: \"Substring to narrow by, e.g. 'threads', 'sql', 'admin', 'semantic'.\"\n },\n all: {\n type: \"boolean\",\n description:\n \"Include ide-only and worker-only routes. Off by default: those are mounted on one \" +\n \"instance of the fleet, so a caller hitting the load balancer cannot reach them directly.\"\n }\n }\n }\n },\n {\n name: \"oxy_schema\",\n description:\n \"The request and response schema for ONE endpoint, so a body can be built correctly. \" +\n \"Covers the data plane (SQL, semantic query, and the org/workspace lookups); a blank \" +\n \"result means undocumented, not nonexistent — use oxy_routes to confirm the endpoint exists.\",\n inputSchema: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"Endpoint path, e.g. '{workspace}/sql/query'.\" },\n method: { type: \"string\", description: \"Narrow to one HTTP method.\" }\n },\n required: [\"path\"]\n }\n },\n {\n name: \"oxy_request\",\n description:\n \"Make an authenticated request to the Oxy API. The credential is picked from the path \" +\n \"(/api/** takes a bearer, /external/api/** an API key). Placeholders {org}, {workspace}, \" +\n \"{project}, {customer} and {me} are substituted from context. \" +\n \"Read freely; ask the human before any mutating request against production.\",\n inputSchema: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"Path relative to /api, e.g. '{org}/workspaces'.\" },\n method: {\n type: \"string\",\n description: \"HTTP method. Defaults to GET, or POST with fields.\"\n },\n fields: {\n type: \"object\",\n description:\n \"Request parameters. Sent as a JSON body on POST/PUT/PATCH, or as query parameters \" +\n \"on GET/HEAD/DELETE. Values keep their JSON type.\"\n },\n jq: {\n type: \"string\",\n description:\n \"A jq program to reduce the response before it is returned. Use it — a full list \" +\n \"response is far more context than the two fields you need.\"\n }\n },\n required: [\"path\"]\n }\n },\n {\n name: \"oxy_whoami\",\n description:\n \"Which deployment this is pointed at and who the token belongs to. \" +\n \"Use it when a call returns 401/403, or a 200 with a null body — on this API an expired \" +\n \"session answers 200 null rather than 401, and this tells the two apart.\",\n inputSchema: { type: \"object\", properties: {} }\n }\n];\n\n/**\n * THE SANDBOX LOOP, AS TOOLS — the deliberate exception to \"four, not one per\n * endpoint\" above. A generic `oxy_request` can call these same routes, but it\n * cannot carry the per-operation validation that makes the loop safe for an\n * agent to drive unsupervised: the dev-<handle> grammar, the confirm-before-\n * delete gate, and the hard refusal of a sandbox publish outside dev-<handle>.\n * Every one of these reuses its CLI verb's own request-building function —\n * see `env.ts` / `fn.ts` / `checks.ts` / `invocations.ts` / `logs.ts` /\n * `publish.ts` — so there is exactly one implementation of each request, and\n * the tool result is the same JSON document that verb's `--json` prints.\n *\n * Each description says what it returns AND what to call next, so the loop\n * reads off the tool list alone: env_create → publish_sandbox → fn_call /\n * checks_run → invocations_list / invocations_held / logs → iterate from\n * publish_sandbox → env_delete.\n */\nconst SANDBOX_TOOLS = [\n {\n name: \"oxy_env_create\",\n description:\n \"Create a dev-<handle> SANDBOX of a custom app — its own build pointer, storage silo, \" +\n \"secrets and Airhouse sibling, isolated from production and from every other sandbox. \" +\n \"Starts with no build. Returns the Environment (status, build, url). Call \" +\n \"oxy_publish_sandbox next to give it one.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n name: {\n type: \"string\",\n description: \"dev-<handle> — 1-12 lowercase letters, digits and single hyphens.\"\n }\n },\n required: [\"app\", \"name\"]\n }\n },\n {\n name: \"oxy_env_list\",\n description:\n \"List a custom app's environments — production, staging and every dev-<handle> sandbox \" +\n \"— each with its status and build. Use it to find a sandbox's name before oxy_env_show, \" +\n \"oxy_publish_sandbox or oxy_env_delete.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' }\n },\n required: [\"app\"]\n }\n },\n {\n name: \"oxy_env_show\",\n description:\n \"One environment's detail — status, build, url. Call it after oxy_publish_sandbox to \" +\n \"confirm a build landed, or before oxy_fn_call to confirm one exists.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n name: { type: \"string\", description: \"production, staging, or dev-<handle>.\" }\n },\n required: [\"app\", \"name\"]\n }\n },\n {\n name: \"oxy_env_delete\",\n description:\n \"DESTRUCTIVE — tears down a sandbox's storage silo, secrets and Airhouse sibling for \" +\n \"good. Requires confirm=true: there is no terminal to ask on here, so the delete refuses \" +\n \"without it, the same way `oxyc env delete` refuses without --yes off a TTY. Set \" +\n \"waitSeconds to avoid polling oxy_env_show yourself.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n name: {\n type: \"string\",\n description: \"dev-<handle> — production and staging can never be deleted.\"\n },\n confirm: { type: \"boolean\", description: \"Must be true, or the call is refused.\" },\n waitSeconds: {\n type: \"number\",\n description: \"Block until torn down, or this many seconds pass (no wait by default).\"\n }\n },\n required: [\"app\", \"name\", \"confirm\"]\n }\n },\n {\n name: \"oxy_publish_sandbox\",\n description:\n \"DESTRUCTIVE (to the sandbox's build, not production) — build the app in `dir` (default: \" +\n \"the current directory) and publish it to a dev-<handle> sandbox's build pointer. \" +\n \"REQUIRES a dev-<handle> appEnv and refuses production, staging, or any attempt to \" +\n \"promote — a sandbox build is never promoted; publish the same tree to staging and \" +\n \"promote that instead, outside this tool. Runs the app's own build command, so it \" +\n \"executes whatever package scripts oxy-app.json declares. Returns the publish result \" +\n \"(build id, url). Call oxy_fn_call or oxy_checks_run with the same appEnv next.\",\n inputSchema: {\n type: \"object\",\n properties: {\n appEnv: {\n type: \"string\",\n description: \"dev-<handle> — anything else (production, staging) is refused.\"\n },\n dir: { type: \"string\", description: \"App directory to publish (default: cwd).\" },\n app: {\n type: \"string\",\n description: \"App slug (default: oxy-app.json's slug, or OXY_APP).\"\n },\n org: {\n type: \"string\",\n description: \"Org slug (default: oxy-app.json's orgSlug, or OXY_ORG).\"\n }\n },\n required: [\"appEnv\"]\n }\n },\n {\n name: \"oxy_fn_call\",\n description:\n \"Call a custom app's Oxy Function directly (POST .../fn/<function>) and return its \" +\n \"result: {ok, status, body, logs, invocationId, error}. isError is set when the \" +\n \"FUNCTION failed (distinct from a transport failure, which throws). Needs a build in \" +\n \"that environment — oxy_publish_sandbox or oxy_env_show confirms one exists. Call \" +\n \"oxy_logs or oxy_invocations_held with the returned invocationId to see why it failed.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n function: { type: \"string\", description: \"The Oxy Function's name.\" },\n appEnv: { type: \"string\", description: \"production (default), staging, or dev-<handle>.\" },\n data: { type: \"string\", description: 'JSON request body (default \"{}\").' },\n timeoutSeconds: { type: \"number\", description: \"Client-side timeout (default 60).\" }\n },\n required: [\"app\", \"function\"]\n }\n },\n {\n name: \"oxy_checks_run\",\n description:\n 'Run every function the app marked \"check\": true in one environment, and wait for each ' +\n \"to a terminal status. Returns {app, checks:[{name, status, passed, error, durationMs}]}; \" +\n \"isError is set when any check failed or timed out. Run this right after \" +\n \"oxy_publish_sandbox to verify a build.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n appEnv: { type: \"string\", description: \"production (default), staging, or dev-<handle>.\" },\n timeoutSeconds: { type: \"number\", description: \"Per-check timeout (default 300).\" }\n },\n required: [\"app\"]\n }\n },\n {\n name: \"oxy_invocations_list\",\n description:\n \"List what ran in an app's environment — every function invocation, newest first, with \" +\n \"its status. Use it to confirm oxy_fn_call or oxy_checks_run reached the sandbox, or to \" +\n \"find an invocation id for oxy_invocations_held.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n appEnv: { type: \"string\", description: \"Narrow to one environment.\" },\n build: { type: \"string\", description: \"Narrow to one build (its id, or the build UUID).\" },\n function: { type: \"string\", description: \"Narrow to one function.\" },\n limit: { type: \"number\", description: \"Max rows (default 50).\" }\n },\n required: [\"app\"]\n }\n },\n {\n name: \"oxy_invocations_held\",\n description:\n \"What a non-production invocation's write policy HELD instead of performing — \" +\n \"production's would-be effect, recorded rather than run. Empty for a production \" +\n \"invocation, since nothing is held there. Call with an id from oxy_invocations_list or \" +\n \"oxy_fn_call's invocationId.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n invocationId: { type: \"string\", description: \"From oxy_invocations_list or oxy_fn_call.\" }\n },\n required: [\"app\", \"invocationId\"]\n }\n },\n {\n name: \"oxy_logs\",\n description:\n \"An app's persisted ctx.log() / console.* output, newest-relevant window first. Use it \" +\n \"to debug a function that oxy_fn_call or oxy_checks_run reported as failed.\",\n inputSchema: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: '\"<org-slug>/<app-slug>\" or the app UUID.' },\n appEnv: {\n type: \"string\",\n description: \"Narrow to one environment (default: production only).\"\n },\n invocation: { type: \"string\", description: \"Narrow to one invocation id.\" },\n request: { type: \"string\", description: \"Narrow to one request id.\" },\n hours: { type: \"number\", description: \"Window size in hours (default 24, max 168).\" },\n limit: { type: \"number\", description: \"Max rows (default 100, max 500).\" }\n },\n required: [\"app\"]\n }\n }\n];\n\n/**\n * WORKSPACE PREVIEWS, AS TOOLS — the same exception as `SANDBOX_TOOLS` above,\n * for the same reason: the run-kind grammar and the create/wait/timeout\n * contract are per-operation validation a generic `oxy_request` cannot carry.\n * Each reuses its `oxyc preview` verb's own request-building function (see\n * `preview.ts` / `preview-runs.ts`), so there is one implementation of each\n * request and the tool result matches that verb's `--json` output.\n *\n * The loop: preview_create (waits for the compile) → preview_checks → \" +\n * preview_run (procedure or airway_sample) → preview_run_show (waits for the \" +\n * run) → preview_runs_list to see transform_build/compare runs the server \" +\n * queued on its own → preview_delete when done.\n */\nconst PREVIEW_TOOLS = [\n {\n name: \"oxy_preview_create\",\n description:\n \"Preview a workspace branch: compile its head into a staging revision (or reuse the \" +\n \"ready one for that commit) and start serving it under x-oxy-preview-revision. Returns \" +\n \"the PreviewItem ({branch, status, revision_id, checks, ...}) — idempotent, so calling it \" +\n \"again on the same commit is free. Set waitSeconds to avoid polling oxy_preview_show \" +\n \"yourself; isError is then set on a compile that failed. Call oxy_preview_checks next.\",\n inputSchema: {\n type: \"object\",\n properties: {\n branch: { type: \"string\", description: \"The branch to preview. Not the default branch.\" },\n waitSeconds: {\n type: \"number\",\n description:\n \"Block until the compile is ready or failed, up to this many seconds (no wait by default).\"\n }\n },\n required: [\"branch\"]\n }\n },\n {\n name: \"oxy_preview_list\",\n description:\n \"List every branch staff are previewing in this workspace, most recently touched first, \" +\n \"each with its compile status and checks summary. Use it to find a branch name before \" +\n \"the other preview tools.\",\n inputSchema: { type: \"object\", properties: {} }\n },\n {\n name: \"oxy_preview_show\",\n description:\n \"One branch's preview — status and checks summary (PreviewItem). Not a server route: \" +\n \"filters oxy_preview_list's result by branch, and fails NOT_FOUND when the branch has \" +\n \"no preview.\",\n inputSchema: {\n type: \"object\",\n properties: { branch: { type: \"string\", description: \"The previewed branch.\" } },\n required: [\"branch\"]\n }\n },\n {\n name: \"oxy_preview_delete\",\n description:\n \"DESTRUCTIVE — stop previewing a branch: cancels its queued runs and releases its \" +\n \"staging revision. Requires confirm=true (there is no terminal to ask on here).\",\n inputSchema: {\n type: \"object\",\n properties: {\n branch: { type: \"string\", description: \"The previewed branch to stop previewing.\" },\n confirm: { type: \"boolean\", description: \"Must be true, or the call is refused.\" }\n },\n required: [\"branch\", \"confirm\"]\n }\n },\n {\n name: \"oxy_preview_checks\",\n description:\n \"The Airway change check of a preview's current revision: each changed .airway.yml \" +\n \"pipeline and whether merging it needs a Reset schema, and each changed automation \" +\n '(\"auto\" transforms are built in the preview and compared with live; \"manual\" ones say ' +\n 'why not). \"pending\" with no pipelines until the check has run — call oxy_preview_show ' +\n \"or retry shortly.\",\n inputSchema: {\n type: \"object\",\n properties: { branch: { type: \"string\", description: \"The previewed branch.\" } },\n required: [\"branch\"]\n }\n },\n {\n name: \"oxy_preview_run\",\n description:\n \"Start a held dry run of a previewed branch: a procedure (every write held or, on the \" +\n \"managed Airhouse, redirected into the preview's own schemas) or an airway_sample (a \" +\n \"bounded window of a pipeline into the preview's schemas). transform_build and compare \" +\n \"runs are queued by the server's own change check and cannot be started here — read \" +\n \"them back with oxy_preview_runs_list / oxy_preview_run_show instead. Returns \" +\n \"{run_id, state}. Set waitSeconds to avoid polling oxy_preview_run_show yourself; isError \" +\n \"is then set if the run failed or was cancelled. Needs OXY_PREVIEW_RUNS enabled on the \" +\n \"deployment.\",\n inputSchema: {\n type: \"object\",\n properties: {\n branch: { type: \"string\", description: \"The previewed branch to run against.\" },\n kind: { type: \"string\", description: '\"procedure\" or \"airway_sample\".' },\n ref: {\n type: \"string\",\n description: \"The automation path (procedure) or .airway.yml path (airway_sample).\"\n },\n variables: {\n type: \"string\",\n description: \"procedure: JSON object of the automation's variables.\"\n },\n readLiveOnly: {\n type: \"boolean\",\n description: \"procedure: read live tables even where the preview holds a copy.\"\n },\n windowFrom: { type: \"string\", description: \"airway_sample: window start, RFC 3339.\" },\n windowTo: { type: \"string\", description: \"airway_sample: window end, RFC 3339.\" },\n resources: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"airway_sample: the resources to read, for a multi-resource source.\"\n },\n waitSeconds: {\n type: \"number\",\n description: \"Block until the run finishes, up to this many seconds (no wait by default).\"\n }\n },\n required: [\"branch\", \"kind\", \"ref\"]\n }\n },\n {\n name: \"oxy_preview_runs_list\",\n description:\n \"A branch's held runs, newest first — procedure, transform_build, compare and \" +\n \"airway_sample alike, with each one's state and outcome. Use it to find a run_id for \" +\n \"oxy_preview_run_show, including the transform_build/compare runs the server queues on \" +\n \"its own that oxy_preview_run cannot start.\",\n inputSchema: {\n type: \"object\",\n properties: { branch: { type: \"string\", description: \"The previewed branch.\" } },\n required: [\"branch\"]\n }\n },\n {\n name: \"oxy_preview_run_show\",\n description:\n \"One run's detail: its steps (with what each held or redirected), and — for \" +\n \"transform_build/compare — the compare-with-live outcome, or — for airway_sample — the \" +\n \"sample's ask and result. Set waitSeconds to avoid polling yourself; isError is then set \" +\n \"if the run failed or was cancelled.\",\n inputSchema: {\n type: \"object\",\n properties: {\n runId: { type: \"string\", description: \"From oxy_preview_run or oxy_preview_runs_list.\" },\n waitSeconds: {\n type: \"number\",\n description: \"Block until the run finishes, up to this many seconds (no wait by default).\"\n }\n },\n required: [\"runId\"]\n }\n }\n];\n\nconst ALL_TOOLS = [...TOOLS, ...SANDBOX_TOOLS, ...PREVIEW_TOOLS];\n\n/**\n * Above this many matches, `oxy_routes` drops the descriptions.\n *\n * DEGRADES, IT DOES NOT REFUSE — the same shape as `oxyc routes`, which picks\n * `renderDetailed` under 40 matches and the compact method+path table above it\n * (`discover.ts`). The expensive field is `description`, not the row: a trimmed\n * row is a few tens of bytes, so several hundred still fit in a result an agent\n * can read, while the descriptions are what turn a broad match into hundreds of\n * KB of context spent before a question is asked.\n *\n * The threshold is on the COUNT, not on whether a `filter` was supplied — a\n * filter is not evidence of narrowing. `searchRoutes` matches a substring of\n * method, path, surface or description, so `\"admin\"`, `\"workspace\"` and\n * `\"query\"` are all filters a model reaches for in good faith that clear this.\n */\nconst MAX_DESCRIBED_ROUTES = 60;\n\n/**\n * And above THIS many, there is nothing useful to return at all.\n *\n * Deliberately far above the threshold that drops descriptions: refusing is the\n * answer only when even the trimmed listing would be a context dump, which for\n * a deployment of ~670 routes means \"you asked for effectively all of them\".\n */\nconst MAX_ROUTES_PER_RESULT = 400;\n\n/** `{ a: 1 }` → the `-f`/`-F` pairs `parseFields` understands. */\nfunction fieldPairs(fields: Record<string, unknown> | undefined): string[] {\n if (!fields) return [];\n return Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`);\n}\n\n/**\n * Check the arguments the schema says are required.\n *\n * `inputSchema.required` is ADVISORY on the low-level `Server` — it is handed\n * to the model, not enforced by the transport. Without this, `oxy_schema` with\n * no `path` matched every path and returned the whole OpenAPI document, and\n * `oxy_request` with none built a request to `/api/`.\n */\nfunction requireArgs(name: string, args: Record<string, unknown>, required: string[]): void {\n const missing = required.filter((k) => {\n const v = args[k];\n return v === undefined || v === null || (typeof v === \"string\" && v.trim() === \"\");\n });\n if (missing.length > 0) {\n throw new CliError(`${name} needs ${missing.join(\", \")}`, { code: ExitCode.USAGE });\n }\n}\n\nasync function callTool(ctx: Context, name: string, args: Record<string, unknown>) {\n switch (name) {\n case \"oxy_routes\": {\n const catalog = await loadCatalog({ target: ctx.target(), bearer: ctx.maybeBearer() });\n const filter = (args.filter as string | undefined)?.trim();\n let matches = searchRoutes(catalog, filter || undefined);\n if (!args.all) matches = matches.filter((r) => r.role === \"fleet-ok\");\n\n // A cache stale enough to be wrong is worth saying INSIDE the result:\n // `loadCatalog` warns on stderr, which an MCP client never shows anyone.\n const staleNote = catalog.stale\n ? \"NOTE: this route table came from a stale local cache; the deployment was unreachable.\\n\\n\"\n : \"\";\n\n const asked = filter ? ` ${JSON.stringify(filter)}` : \"\";\n\n // Only a match set too large even to LIST is refused. Below that the\n // result degrades instead, because a filter like \"admin\" or \"query\" can\n // clear the description threshold in good faith and \"guess something\n // narrower\" is a dead end for a model with no way to see what it hit.\n if (matches.length > MAX_ROUTES_PER_RESULT) {\n return text(\n `${staleNote}${matches.length} routes match${asked}, which is effectively the whole ` +\n `deployment. Call oxy_routes again with a narrower \\`filter\\` (matched against ` +\n `method, path, surface and description), e.g. \"threads\", \"sql\", \"semantic\", \"admin\".`,\n true\n );\n }\n\n if (matches.length === 0) {\n // An empty array reads as \"this deployment has no such endpoint\",\n // which is usually false — the filter was wrong, or the route is\n // ide-only and hidden by default.\n return text(\n `${staleNote}No route matches ${JSON.stringify(filter ?? \"\")}. Try a broader filter, ` +\n `or pass all=true to include ide-only and worker-only mounts.`,\n true\n );\n }\n // Trimmed to the fields that help a caller build a request. The full\n // record carries the handler path and mount notes, which are for a\n // maintainer reading source, not for an agent composing a call.\n const described = matches.length <= MAX_DESCRIBED_ROUTES;\n // `credential` SURVIVES THE TRIM. It is what separates the bearer mount\n // from the `/external/api` API-key one, so a caller composing a request\n // needs it in either form — and `renderCompact`, the CLI shape this\n // mirrors, keeps it as a per-surface heading. It is also not the\n // expensive field: ~25 bytes a row, ~10 KB across the whole ceiling,\n // against descriptions that run to hundreds of KB.\n const trimmed = matches.map((r) =>\n described\n ? { method: r.method, path: r.path, credential: r.credential, description: r.description }\n : { method: r.method, path: r.path, credential: r.credential }\n );\n // The note says the listing is trimmed AND how to get the descriptions\n // back. Without it a model reads a description-less row as an endpoint\n // that has no documentation, rather than one it did not ask narrowly\n // enough to see.\n const trimNote = described\n ? \"\"\n : `NOTE: ${matches.length} routes match${asked} — too many to describe, so each row is ` +\n `method, path and credential WITHOUT its description. Narrow the \\`filter\\` to ` +\n `${MAX_DESCRIBED_ROUTES} or fewer matches to get the descriptions back.\\n\\n`;\n return text(`${staleNote}${trimNote}${JSON.stringify(trimmed)}`);\n }\n\n case \"oxy_schema\": {\n requireArgs(\"oxy_schema\", args, [\"path\"]);\n const doc = (await loadOpenApi({ target: ctx.target(), bearer: ctx.maybeBearer() })) as {\n paths?: Record<string, Record<string, unknown>>;\n components?: unknown;\n };\n const wanted = typeof args.path === \"string\" ? args.path : \"\";\n const method = args.method as string | undefined;\n // `comparablePath` is imported from `discover.ts` rather than copied:\n // a second reduction of the same two spellings is a second thing to keep\n // in step, and the copy here was untested. Exact first, then substring —\n // the order `runSchema` uses, so the two commands answer alike.\n const paths = Object.entries(doc.paths ?? {});\n const exact = paths.filter(([p]) => comparablePath(p) === comparablePath(wanted));\n const hit =\n exact.length > 0\n ? exact\n : paths.filter(([p]) => comparablePath(p).includes(comparablePath(wanted)));\n if (hit.length === 0) {\n return text(\n `No documented schema for ${wanted}. The OpenAPI document covers the data plane only — ` +\n `call oxy_routes with a filter to confirm the endpoint exists.`,\n true\n );\n }\n const selected = Object.fromEntries(\n hit.map(([p, ops]) => [\n p,\n method\n ? Object.fromEntries(\n Object.entries(ops).filter(([verb]) => verb.toLowerCase() === method.toLowerCase())\n )\n : ops\n ])\n );\n return text(JSON.stringify({ paths: selected, components: doc.components }, null, 2));\n }\n\n case \"oxy_request\": {\n requireArgs(\"oxy_request\", args, [\"path\"]);\n const raw = typeof args.path === \"string\" ? args.path : \"\";\n const method =\n (typeof args.method === \"string\" ? args.method.toUpperCase() : \"\") || undefined;\n const fields = parseFields([], fieldPairs(args.fields as Record<string, unknown>));\n const verb = method ?? (fields.present ? \"POST\" : \"GET\");\n const carriesBody = ![\"GET\", \"HEAD\", \"DELETE\"].includes(verb);\n\n let path = substitutePlaceholders(normalizePath(raw), ctx.placeholders());\n let body: string | undefined;\n if (fields.present) {\n if (carriesBody) body = JSON.stringify(fields.params);\n else {\n const query = paramsToQuery(fields.params);\n if (query) path += (path.includes(\"?\") ? \"&\" : \"?\") + query;\n }\n }\n\n const apiKey = ctx.apiKey();\n const external = isExternalSurface(path);\n const response = await request({\n target: ctx.target(),\n path,\n method: verb,\n body,\n bearer: external && apiKey ? ctx.maybeBearer() : ctx.bearer(),\n apiKey\n });\n\n if (response.status < 200 || response.status >= 300) {\n // The STATUS and the body, not a thrown error: the model can act on\n // \"403, here is what the server said\" and cannot act on a transport\n // exception. It is marked `isError` so the runtime shows it as one.\n return text(`HTTP ${response.status} ${response.statusText}\\n${response.body}`, true);\n }\n\n if (response.body.trim() === \"null\") {\n return text(\n \"null\\n\\n(NOTE: a 200 with a null body can mean an expired session on this API, \" +\n \"not 'no such thing'. Call oxy_whoami to tell them apart.)\"\n );\n }\n\n const jq = args.jq as string | undefined;\n if (jq) return text(runJq(response.body, jq));\n return text(response.body);\n }\n\n case \"oxy_whoami\": {\n const target = ctx.target();\n const response = await request({\n target,\n path: \"/api/user\",\n method: \"GET\",\n bearer: ctx.bearer()\n });\n const payload = parseJson(response.body);\n if (payload === null || payload === undefined) {\n return text(\n `target: ${target}\\nThe token no longer resolves to a user — the session has expired. ` +\n `Ask the human to run \\`oxyc login\\`.`,\n true\n );\n }\n return text(JSON.stringify({ target, user: payload }, null, 2));\n }\n\n // ── sandbox loop ───────────────────────────────────────────────────────\n\n case \"oxy_env_create\": {\n requireArgs(\"oxy_env_create\", args, [\"app\", \"name\"]);\n return jsonResult(await envCreate(ctx, String(args.app), String(args.name)));\n }\n\n case \"oxy_env_list\": {\n requireArgs(\"oxy_env_list\", args, [\"app\"]);\n return jsonResult({ environments: await envList(ctx, String(args.app)) });\n }\n\n case \"oxy_env_show\": {\n requireArgs(\"oxy_env_show\", args, [\"app\", \"name\"]);\n return jsonResult(await envShow(ctx, String(args.app), String(args.name)));\n }\n\n case \"oxy_env_delete\": {\n requireArgs(\"oxy_env_delete\", args, [\"app\", \"name\"]);\n if (args.confirm !== true) {\n throw usageError(\n \"oxy_env_delete needs confirm=true\",\n \"there is no terminal to ask on here — pass confirm=true once you mean it\"\n );\n }\n const result = await envDelete(ctx, String(args.app), String(args.name), {\n yes: true,\n waitSeconds: args.waitSeconds === undefined ? undefined : Number(args.waitSeconds)\n });\n return jsonResult(result);\n }\n\n case \"oxy_publish_sandbox\": {\n requireArgs(\"oxy_publish_sandbox\", args, [\"appEnv\"]);\n // Client-side, before any build or request — the same grammar `oxyc\n // publish --app-env` validates, and the same refusal of \"production\" /\n // \"staging\": a sandbox publish tool that accepted either would be the\n // promote path this tool exists to foreclose.\n const appEnv = requireSandboxName(String(args.appEnv));\n // `org` is a GLOBAL flag (`resolveIdentity` reads `ctx.flags.org`), not a\n // `PublishFlags` field — a fresh context carries it the way `--org`\n // would on the CLI, without mutating the one `runMcp` was given.\n const org = args.org as string | undefined;\n const publishCtx = org === undefined ? ctx : createContext({ ...ctx.flags, org }, ctx.cwd);\n const outcome = await publish(publishCtx, {\n appEnv,\n dir: args.dir as string | undefined,\n app: args.app as string | undefined\n });\n return jsonResult(outcome.result ?? { bundle_dir: outcome.bundleDir });\n }\n\n case \"oxy_fn_call\": {\n requireArgs(\"oxy_fn_call\", args, [\"app\", \"function\"]);\n const result = await fnCall(ctx, String(args.app), String(args.function), {\n appEnv: args.appEnv as string | undefined,\n data: args.data as string | undefined,\n timeoutSeconds: args.timeoutSeconds === undefined ? 60 : Number(args.timeoutSeconds)\n });\n return jsonResult(result, { isError: !result.ok, code: ExitCode.FAILURE });\n }\n\n case \"oxy_checks_run\": {\n requireArgs(\"oxy_checks_run\", args, [\"app\"]);\n const report = await runChecksCore(ctx, String(args.app), {\n timeoutSeconds: args.timeoutSeconds === undefined ? 300 : Number(args.timeoutSeconds),\n appEnv: args.appEnv as string | undefined\n });\n const failed = report.checks.some((c) => !c.passed);\n return jsonResult(report, { isError: failed, code: ExitCode.CHECK_FAILED });\n }\n\n case \"oxy_invocations_list\": {\n requireArgs(\"oxy_invocations_list\", args, [\"app\"]);\n const invocations = await invocationsList(ctx, String(args.app), {\n appEnv: args.appEnv as string | undefined,\n build: args.build as string | undefined,\n fn: args.function as string | undefined,\n limit: args.limit === undefined ? undefined : Number(args.limit)\n });\n return jsonResult({ invocations });\n }\n\n case \"oxy_invocations_held\": {\n requireArgs(\"oxy_invocations_held\", args, [\"app\", \"invocationId\"]);\n return jsonResult(await invocationsHeld(ctx, String(args.app), String(args.invocationId)));\n }\n\n case \"oxy_logs\": {\n requireArgs(\"oxy_logs\", args, [\"app\"]);\n const logs = await fetchLogs(ctx, String(args.app), {\n appEnv: args.appEnv as string | undefined,\n invocation: args.invocation as string | undefined,\n request: args.request as string | undefined,\n hours: args.hours === undefined ? undefined : Number(args.hours),\n limit: args.limit === undefined ? undefined : Number(args.limit)\n });\n return jsonResult({ logs });\n }\n\n // ── workspace previews ──────────────────────────────────────────────────\n\n case \"oxy_preview_create\": {\n requireArgs(\"oxy_preview_create\", args, [\"branch\"]);\n const item = await previewCreate(ctx, String(args.branch), {\n waitSeconds: args.waitSeconds === undefined ? undefined : Number(args.waitSeconds)\n });\n return jsonResult(item, { isError: previewFailed(item), code: ExitCode.FAILURE });\n }\n\n case \"oxy_preview_list\":\n return jsonResult({ items: await previewList(ctx) });\n\n case \"oxy_preview_show\": {\n requireArgs(\"oxy_preview_show\", args, [\"branch\"]);\n return jsonResult(await previewShow(ctx, String(args.branch)));\n }\n\n case \"oxy_preview_delete\": {\n requireArgs(\"oxy_preview_delete\", args, [\"branch\"]);\n if (args.confirm !== true) {\n throw usageError(\n \"oxy_preview_delete needs confirm=true\",\n \"there is no terminal to ask on here — pass confirm=true once you mean it\"\n );\n }\n return jsonResult(await previewDelete(ctx, String(args.branch), { yes: true }));\n }\n\n case \"oxy_preview_checks\": {\n requireArgs(\"oxy_preview_checks\", args, [\"branch\"]);\n return jsonResult(await previewChecks(ctx, String(args.branch)));\n }\n\n case \"oxy_preview_run\": {\n requireArgs(\"oxy_preview_run\", args, [\"branch\", \"kind\", \"ref\"]);\n const kind = requireRunKind(String(args.kind));\n const windowFrom = args.windowFrom as string | undefined;\n const windowTo = args.windowTo as string | undefined;\n const submitted = await previewSubmitRun(ctx, {\n branch: String(args.branch),\n kind,\n ref: String(args.ref),\n variables: parseVariables(args.variables as string | undefined),\n readLiveOnly: args.readLiveOnly as boolean | undefined,\n window:\n windowFrom === undefined && windowTo === undefined\n ? undefined\n : { from: windowFrom, to: windowTo },\n resources: (args.resources as string[] | undefined) ?? []\n });\n const waitSeconds = args.waitSeconds === undefined ? undefined : Number(args.waitSeconds);\n if (waitSeconds === undefined) return jsonResult(submitted);\n const detail = await previewRunGet(ctx, submitted.run_id, { waitSeconds });\n return jsonResult(detail, { isError: runFailed(detail), code: ExitCode.FAILURE });\n }\n\n case \"oxy_preview_runs_list\": {\n requireArgs(\"oxy_preview_runs_list\", args, [\"branch\"]);\n return jsonResult({ runs: await previewRunsList(ctx, String(args.branch)) });\n }\n\n case \"oxy_preview_run_show\": {\n requireArgs(\"oxy_preview_run_show\", args, [\"runId\"]);\n const detail = await previewRunGet(ctx, String(args.runId), {\n waitSeconds: args.waitSeconds === undefined ? undefined : Number(args.waitSeconds)\n });\n return jsonResult(detail, { isError: runFailed(detail), code: ExitCode.FAILURE });\n }\n\n default:\n return text(`unknown tool: ${name}`, true);\n }\n}\n\n/**\n * Serve MCP on stdio until the client disconnects.\n *\n * STDIO IS THE WHOLE TRANSPORT, deliberately. An HTTP/SSE server would need a\n * port, a lifetime and an auth story of its own; stdio is what every agent\n * runtime already launches, and the process inherits the caller's environment\n * — which is where the token and the target come from.\n *\n * NOTHING MAY BE WRITTEN TO STDOUT except protocol frames. `log.info` and\n * friends already go to stderr, which is why this can share the CLI's own\n * plumbing without corrupting the stream.\n */\nexport async function runMcp(ctx: Context): Promise<void> {\n const server = new Server({ name: \"oxyc\", version: VERSION }, { capabilities: { tools: {} } });\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: ALL_TOOLS }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n try {\n return await callTool(\n ctx,\n req.params.name,\n (req.params.arguments ?? {}) as Record<string, unknown>\n );\n } catch (cause) {\n // A throw here would kill the session. The model can act on a message;\n // it cannot act on a dead transport.\n const message = (cause as Error).message ?? String(cause);\n // BOTH CHANNELS. A model gets one string, so there is no marker to\n // distinguish them — but reading `hint` alone drops the one line that\n // says what to DO, which is the half a model can act on.\n const { hint, remedy } = cause as { hint?: string; remedy?: string };\n // Blank line between them, the way every other renderer of these two\n // separates them — a model given two consecutive imperatives with no\n // boundary loses the distinction the second field exists to make.\n // `!== undefined` for the narrowing TS gives it, and a length check\n // because an empty string would open the block with a blank line —\n // `filter(Boolean)` covered that and cost the narrowing, so both.\n const extra = [hint, remedy].filter((line) => line !== undefined && line !== \"\").join(\"\\n\\n\");\n // THE EXIT-CODE CLASS, APPENDED — not substituted for the message, and\n // not only on the sandbox/preview tools: every CliError carries a code,\n // so every tool's failure now lets a caller branch the way it would on\n // `oxyc`'s own exit code, by NAME rather than a digit it has to look up.\n // The server's own `code` field (previews, sandboxes) rides along when\n // the response carried one.\n const codeLine =\n cause instanceof CliError\n ? `[exit ${cause.code} ${exitCodeName(cause.code)}]` +\n (cause.serverCode ? ` server code: ${cause.serverCode}` : \"\")\n : undefined;\n const body = [message, extra, codeLine]\n .filter((s) => s !== undefined && s !== \"\")\n .join(\"\\n\\n\");\n return text(body, true);\n }\n });\n\n await server.connect(new StdioServerTransport());\n\n // `connect` returns once the transport is wired; the process must stay up\n // until the client closes stdin.\n await new Promise<void>((resolve) => {\n process.stdin.on(\"close\", resolve);\n server.onclose = resolve;\n });\n}\n"],"mappings":"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"}