Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 4 additions & 39 deletions .github/workflows/pullfrog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ permissions:
jobs:
pullfrog:
runs-on: ubuntu-latest
# Provider secrets live in this environment, which only main may deploy to,
# so a dispatch on a pushed branch can't reach them.
# Only main may deploy to this environment, so a dispatch on a pushed
# branch can't mint the OIDC token Pullfrog trades for model access.
environment: pullfrog
permissions:
id-token: write
Expand All @@ -33,40 +33,5 @@ jobs:
uses: pullfrog/pullfrog@895164fc90f5c493e72693c919c6cf269af158eb # v0.1.110
with:
prompt: ${{ inputs.prompt }}
env:
# add at least one provider API key
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
META_MODEL_API_KEY: ${{ secrets.META_MODEL_API_KEY }}

# for Amazon Bedrock (https://docs.pullfrog.com/bedrock)
# AWS_BEARER_TOKEN_BEDROCK: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# AWS_REGION: us-east-1
# BEDROCK_MODEL_ID: <bedrock-model-id>

# for Google Vertex AI (https://docs.pullfrog.com/vertex)
# VERTEX_SERVICE_ACCOUNT_JSON: >-
# ${{ secrets.VERTEX_SERVICE_ACCOUNT_JSON }}
# GOOGLE_CLOUD_PROJECT: my-project
# VERTEX_LOCATION: global
# VERTEX_MODEL_ID: <vertex-model-id>

# for Azure OpenAI (https://docs.pullfrog.com/azure)
# AZURE_RESOURCE_NAME is the <name> in https://<name>.openai.azure.com
# AZURE_DEPLOYMENT is your deployment's name, not the model it serves
# AZURE_RESOURCE_NAME: my-resource
# AZURE_API_KEY: ${{ secrets.AZURE_API_KEY }}
# AZURE_DEPLOYMENT: <deployment-name>
# both limits are required - set them to the real limits of that model
# AZURE_CONTEXT: "400000"
# AZURE_MAX_OUTPUT: "128000"

# for any OpenAI-compatible endpoint - LiteLLM, Cloudflare AI Gateway,
# self-hosted vLLM (https://docs.pullfrog.com/openai-compatible)
# OPENAI_COMPATIBLE_BASE_URL: https://litellm.example.com/v1
# OPENAI_COMPATIBLE_API_KEY: ${{ secrets.OPENAI_COMPATIBLE_API_KEY }}
# OPENAI_COMPATIBLE_MODEL: <model-id>
# both limits are required - set them to the real limits of that model
# OPENAI_COMPATIBLE_CONTEXT: "128000"
# OPENAI_COMPATIBLE_MAX_OUTPUT: "16384"
# No provider keys from this repository: model access is configured
# in the Pullfrog console (https://docs.pullfrog.com/model-access).
153 changes: 115 additions & 38 deletions quest/m0/ci/read-only-secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,41 +35,118 @@ JSR's repository OIDC trust while the token is unset. The repository's
`MBX_R2_*` pair is read-only: a branch's write to `mbx-moq-dev` is refused
with AccessDenied.

Verified 2026-11-03 during the [landing recheck](https://github.com/moq-dev/moq/blob/17a81d6c64/quest/m0/ci/land.md):
complete paginated secret metadata lists neither `APP_CLIENT_ID` nor
`APP_PRIVATE_KEY` at organization or repository scope. The `bot` and
`publish` Environments each list both names. The App-copy removal is done;
other write-capable repository secrets below still remain. No secret values
were read, and the throwaway-branch verification has not run.

What remains, in order. Deleting a repository-level secret earlier breaks a
publish, since jobs that do not name `publish` still read it:

1. Backport the workflow changes to `release`. Release pushes and release
tags run `release`'s own copies of the workflows, which differ widely from
`main`'s.
2. The maintainer copies each value into the `publish` Environment:
`CARGO_REGISTRY_TOKEN`, `CLOUDFLARE_API_TOKEN`, `DOCKER_USERNAME`,
`DOCKER_PASSWORD`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD`,
`SIGNING_KEY`, `SIGNING_PASSWORD`, `WINGET_TOKEN`, `R2_ACCESS_KEY_ID`,
`R2_SECRET_ACCESS_KEY` and `R2_ACCOUNT_ID`.
`DISCORD_WEBHOOK` is already in the `alert` Environment, and
`CACHIX_AUTH_TOKEN` moves nowhere: `main` no longer uses Cachix
([publish off main](/quest/m0/ci/publish-release-only.md)).
3. Then deletes those repository secrets, the unused ones
(`GCP_SERVICE_ACCOUNT_KEY`, `CLAUDE_CODE_OAUTH_TOKEN`,
`META_MODEL_API_KEY`), and this repository's access to the
organization's `DISCORD_WEBHOOK`. `MBX_R2_*` stays.
4. The maintainer names the Environment in each trusted publisher: npm's
(`npm`, for every `@moq/*` package), PyPI's (`pypi`, for `moq-rs` and
`moq-ffi`) and pub.dev's (`pub.dev`, for `moq` and `moq_ffi`). Until then a
branch can still claim their OIDC identity by copying the workflow file.
5. JSR cannot require an Environment, so the maintainer creates a JSR publish
token for the `@moq` scope, adds it as `JSR_TOKEN` in the `npm`
Environment, then unlinks the GitHub repository from each JSR package
(`@moq/auth`, `clock`, `hang`, `json`, `loc`, `msf`, `net`, `pattern`,
`qmux`, `room`, `signals`, `video`) so a branch cannot publish through
JSR's OIDC trust. Until the token exists, unlinking pauses JSR
publishing.
6. Verify with the throwaway branch in the Goal, which also confirms each
Environment refuses the branch.
Done in [moq-dev/moq#7202](https://github.com/moq-dev/moq/pull/7202), merged
into `release` 2026-10-31: the workflow backport, including its legacy Cachix
publishers and JSR token handling. For those publishers `publish` also admits
`moq-boy-v*`, `moq-gst-v*`, and `libmoq-v*` tags, and the `release tags`
ruleset also covers `libmoq-v*`. Verified 2026-11-03 during the
[landing recheck](https://github.com/moq-dev/moq/blob/17a81d6c64/quest/m0/ci/land.md):
neither `APP_CLIENT_ID` nor `APP_PRIVATE_KEY` remains at organization or
repository scope; `bot` and `publish` each hold both.

Done 2026-11-05 (maintainer, /quest-unblock): `publish` holds
`CARGO_REGISTRY_TOKEN`, `CLOUDFLARE_API_TOKEN`, `DOCKER_USERNAME`,
`DOCKER_PASSWORD`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_PASSWORD`,
`SIGNING_KEY`, `SIGNING_PASSWORD`, `WINGET_TOKEN`, `R2_ACCESS_KEY_ID`,
`R2_SECRET_ACCESS_KEY`, and `R2_ACCOUNT_ID` (check by name with
`gh secret list --repo moq-dev/moq --env publish`). Checked then:
`SIGNING_KEY`'s fingerprint ends `8FBE8982FDD3FCA2`, the key that signs
`dev.moq:moq` 0.5.3 and `moq-ffi` 0.4.22 on Maven Central; `WINGET_TOKEN` is
a fresh classic PAT owned by kixelated with only `public_repo`, which holds
the microsoft/winget-pkgs fork; the R2 pair lists both `apt-moq-dev` and
`rpm-moq-dev`. `SIGNING_PASSWORD` was not tested against the key.

Decided 2026-11-05 (maintainer, /quest-unblock): ✅ delete the repository
copies only after the next combined patch cut on `release` (release-plz PR
[moq-dev/moq#7936](https://github.com/moq-dev/moq/pull/7936)) publishes
through `publish` cleanly. Environment secrets shadow repository ones of the
same name, so that run proves the release jobs read the Environment's copies,
and proves `SIGNING_PASSWORD` if a Kotlin, apt, or rpm publish runs. Not
chosen: deleting them now.

Decided 2026-11-05 (maintainer, /quest-unblock): ✅ retire Cachix instead of
moving `CACHIX_AUTH_TOKEN`. `release`'s `cachix.yml` still reads it on
`moq-relay-v*`, `moq-cli-v*`, `moq-boy-v*`, `libmoq-v*`, and `moq-gst-v*`
tags; that job names `publish`, which lacks it, so it reads the repository
copy. (`release`'s `cache.yml` also reads it but fires only on pushes to
Comment on lines +68 to +71

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This describes release before the Cachix backport landed. Commit 252144833 (2026-11-06 19:53Z, about three and a half hours before this commit) deleted release's .github/workflows/cachix.yml and replaced its cache.yml with main's, so git grep CACHIX_AUTH_TOKEN origin/release is empty: nothing on main or release reads the token any more. The copy is now held only as the fallback until a tag substitutes from nix.moq.dev, which is what cachix-retire is waiting on.

Technical details
# Stale Cachix read path in the read-only-secrets record

## Affected sites
- `quest/m0/ci/read-only-secrets.md:68-71` — says `release`'s `cachix.yml` still reads the token, so the job falls back to the repository copy
- `quest/m0/ci/read-only-secrets.md:71-72` — says `release`'s `cache.yml` also reads it

## Evidence
- `252144833` (`ci: release tags push to nix.moq.dev instead of Cachix (backport)`) deleted `.github/workflows/cachix.yml` on `release` and rewrote its `cache.yml` to set `NIX_SIGNING_KEY` / `NIX_R2_*`.
- `git grep CACHIX_AUTH_TOKEN origin/release` and `git grep CACHIX_AUTH_TOKEN origin/main -- .github/ infra/` both return nothing.

## Required outcome
- The paragraph should say that no workflow reads the token, and that `cachix-retire` holds the copy only as the fallback until a release tag proves `nix.moq.dev`, so the throwaway-branch check still waits on its deletion.

`main`, so it never runs there.) The m0 quest [cachix-retire](/quest/m0/ci/cachix-retire.md),
from [moq-dev/moq#7982](https://github.com/moq-dev/moq/pull/7982), backports
`release-nix.yml` to `release`, drops `cachix.yml` there, and deletes
`CACHIX_AUTH_TOKEN` and the `kixelated` cache once a release tag proves
nix.moq.dev. So the deletions here leave `CACHIX_AUTH_TOKEN` to that quest,
Comment on lines +74 to +76

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve the Cachix cache when revoking write access

This summary says cachix-retire will delete the kixelated cache, but that quest explicitly records the opposite maintainer decision: revoke the token and retain the cache read-only because older release flakes only name kixelated.cachix.org. Following this plan would remove those tags' binary substitutes and force source builds. Change this to delete/revoke CACHIX_AUTH_TOKEN while keeping the existing cache read-only, matching quest/m0/ci/cachix-retire.md.

and the throwaway-branch check waits for it. Not chosen: copying it into
`publish` now; leaving it at repository scope until the m2 cut.

Done 2026-11-06 (maintainer, /quest-unblock): trusted publishers name
their Environment. npm: `npm` is set and verified on `@moq/net`, `auth`,
`boy`, `hang`, `json`, `loc`, `msf`, `pattern`, `publish`, `room`, `watch`,
and `signals` (the last two by hand, since npm's Cloudflare blocked the
automated save). `@moq/qmux` is published by
moq-dev/web-transport, so it is out of scope here; `@moq/clock`, `libav`,
`ui`, and `video` are not published yet, and
[npm-first-publish](/quest/m3/release/npm-first-publish.md) claims them with
Comment on lines +85 to +87

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm-first-publish claims only @moq/clock, @moq/libav, and @moq/video (its Goal and checks, and quest/m3/release/README.md), not @moq/ui. @moq/ui is not private, has a release script, and 404s on npm, so the cut will attempt its first publish with no trusted publisher, which is the failure npm-first-publish exists to prevent. Add ui there (or record where its first publish is claimed) and adjust this sentence.

Technical details
# `@moq/ui` has no first-publish tracking

## Affected sites
- `quest/m0/ci/read-only-secrets.md:85-87` — lists `clock`, `libav`, `ui`, `video` as claimed by `npm-first-publish`
- `quest/m3/release/npm-first-publish.md:3-11` — Goal and checks name only `clock`, `libav`, `video`

## Evidence
- `js/ui/package.json`: no `"private": true`, `scripts.release` is `bun ../common/release.ts`, version `0.1.0`.
- `https://registry.npmjs.org/@moq/ui/latest` returns 404, so the package does not exist yet.
- `.github/workflows/release-js.yml` runs `bun --filter '*' release`, so every non-private workspace package with a `release` script publishes.
- `@moq/p2p` and `@moq/crypto` are both `private`, so excluding them is correct.

## Required outcome
- Either `@moq/ui` joins `npm-first-publish`'s Goal and checks, or this sentence stops saying that quest covers it and names where its first publish is tracked.

the `npm` Environment. PyPI: `moq-rs` already required `pypi`, and `moq-ffi`'s
any-environment publisher was replaced by one requiring `pypi` (verified on
its settings page). pub.dev: the maintainer set the required environment
`pub.dev` on `moq` and `moq_ffi` and reported both pages show it; agents
cannot view pub.dev admin pages.

Decided 2026-11-06 (maintainer, /quest-unblock): ✅ drop JSR rather than
create a `JSR_TOKEN`. The m0 quest [jsr-retire](/quest/m0/ci/jsr-retire.md),
from [moq-dev/moq#8039](https://github.com/moq-dev/moq/pull/8039), owns removing
JSR publishing and its repository OIDC trust, so this quest no longer
creates a token or unlinks packages. Not chosen: keeping JSR behind a
`JSR_TOKEN` in the `npm` Environment.

Checked 2026-11-06: 3 of [moq-dev/moq#7936](https://github.com/moq-dev/moq/pull/7936)'s
14 tag runs failed on their first attempt. Cachix `moq-relay-v0.17.13`
([run 42129037703](https://github.com/moq-dev/moq/actions/runs/42129037703)):
the macOS and aarch64-linux jobs, both started 23:23:25Z, failed with
"Unable to fetch the information for the environment 'publish' targeted by
this job", while the ubuntu job in the same run, started 41 seconds later,
passed through `publish`. `publish`'s deployment policy admits
`moq-relay-v*` tags (with `main`, `release`, and the `libmoq`, `moq-boy`,
`moq-cli`, `moq-ffi`, and `moq-gst` tag patterns), so this was a transient
Comment on lines +107 to +109

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tag patterns are right, but publish no longer admits main: the live policy is release plus the six tag patterns. That also suggests publish-release-only's remaining step (dropping main) is already done, while infra/README.md still says publish keeps admitting it. Worth confirming what changed and making this sentence, that quest, and the README agree.

Technical details
# `publish` admits `release` and the tag patterns, not `main`

## Affected sites
- `quest/m0/ci/read-only-secrets.md:107-109` — "admits `moq-relay-v*` tags (with `main`, `release`, and the ... tag patterns)"
- `quest/m0/ci/publish-release-only.md` — "What remains, in order" step 2 still lists dropping `main`
- `infra/README.md:69-70` — "`publish` keeps admitting `main` until the maintainer drops it"

## Evidence
- `gh api repos/moq-dev/moq/environments/publish/deployment-branch-policies` returns `total_count: 7`: `release`, `libmoq-v*`, `moq-boy-v*`, `moq-cli-v*`, `moq-ffi-v*`, `moq-gst-v*`, `moq-relay-v*`.
- No job on `main` needs the `main` admission: every reader of a deleted secret is tag-gated or runs on `release`.

## Required outcome
- This sentence matches the live policy; if the drop was deliberate, record it (and retire that step of `publish-release-only`), otherwise restore the policy.

GitHub API failure, not a policy gap. Cachix `moq-gst-v0.4.23`
([run 42129724193](https://github.com/moq-dev/moq/actions/runs/42129724193))
hit a network fetch failure, and moq-gst `moq-gst-v0.4.23`
([run 42129723974](https://github.com/moq-dev/moq/actions/runs/42129723974))
timed out in `brew test` on the aarch64-apple-darwin tarball, which skipped
its Release job. None of the three points at the Environment's secrets.

Done 2026-11-06 (maintainer, /quest-unblock): the three failed tag runs
were re-run; Cachix for `moq-relay-v0.17.13` and `moq-gst-v0.4.23` passed,
and the moq-gst macOS `brew test` hang is unrelated to secrets (planned in
[moq-dev/moq#8076](https://github.com/moq-dev/moq/pull/8076)). After
checking that every job on `main` and `release` reads them through `publish`
or gates the step to tags, the maintainer deleted 15 repository secrets: the
twelve `publish` secrets above, the unreferenced `GCP_SERVICE_ACCOUNT_KEY`,
and Pullfrog's `CLAUDE_CODE_OAUTH_TOKEN` and `META_MODEL_API_KEY`. The
repository secrets left are `CACHIX_AUTH_TOKEN` (owned by `cachix-retire`)
and the read-only `MBX_R2_ACCESS_KEY_ID` and `MBX_R2_SECRET_ACCESS_KEY`.

Decided 2026-11-06 (maintainer, /quest-unblock): ✅ Pullfrog gets no
provider keys from this repository, so `pullfrog.yml` passes none; its
model access comes from the Pullfrog console, which the action reaches with
the job's OIDC token through the `pullfrog` Environment (main only). Not
chosen: moving the keys into the `pullfrog` Environment.

Decided 2026-11-06 (maintainer, /quest-unblock): ✅ leave the organization
secret `DISCORD_WEBHOOK` visible to all repositories; this repository's
`alert` job reads the `alert` Environment's copy. Not chosen: narrowing it to
selected repositories.

Once `cachix-retire` deletes `CACHIX_AUTH_TOKEN`, an agent runs the
throwaway-branch check in the Goal, which also confirms each Environment
refuses the branch, then deletes the branch. The branch may still see
`MBX_R2_*` and `DISCORD_WEBHOOK`, by the decisions above; any other secret
fails the check.
Comment on lines +139 to +143

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Require JSR trust retirement before completing the final check

The replacement completion sequence waits only for cachix-retire, while jsr-retire is merely Related. However, the linked JSR quest still has to archive the packages to remove their repository-wide OIDC trust; #8039 added that plan rather than performing the retirement. If Cachix finishes first, a branch can pass the secret-presence and Environment checks here while retaining an OIDC identity JSR accepts, violating this quest's Goal. Make JSR archival/trust removal an explicit prerequisite (for example under Required) and require its verification before declaring this work complete.


## Required

- [Retire Cachix](/quest/m0/ci/cachix-retire.md) - deletes `CACHIX_AUTH_TOKEN`, which the throwaway-branch check needs gone

## Related

- [Stop publishing to JSR](/quest/m0/ci/jsr-retire.md) - removes JSR publishing and its repository OIDC trust, so no `JSR_TOKEN` is created
- [Drop main from publish](/quest/m0/ci/publish-release-only.md) - narrows `publish` to `release` and release tags once `main` merges into `release`