Skip to content

feat: one-step soothfast action with bot landing - #164

Merged
Verdenroz merged 1 commit into
feat/soothfast-bot-workflowsfrom
feat/soothfast-action
Sep 6, 2026
Merged

Verdenroz merged 1 commit into
feat/soothfast-bot-workflowsfrom
feat/soothfast-action

Conversation

@Verdenroz

@Verdenroz Verdenroz commented Sep 6, 2026

Copy link
Copy Markdown
Owner

What changed

The root action.yml was "install the CLI". It is now the whole adopter surface: one uses: Verdenroz/soothfast@<tag> step in an existing workflow gates pull requests and lands regenerated files as soothfast-bot on the default branch. Everything it runs lives in action/*.sh, the same scripts this repo's own workflows call, so the adopter path is exercised here on every merge.

  • Extended action.yml with prepare, gate, regen, bot-token, and land steps, dispatching on github.event_name and the default branch. New inputs: binary, packages (auto-detected from cargo metadata when empty), gate, changelog, spec, baseline, rustdoc-toolchain, token, bot-token, bot-slug, broker. Added branding for the Marketplace.
  • Added action/prepare.sh (default-branch lookup, early exit when a run has nothing to do, package detection, unshallow, base-ref fetch, valgrind on Debian-family runners, rustdoc toolchain), action/gate.sh (gate every package, last forty lines of each in one PR comment, failed output so the triage upload happens before the step fails), action/comment.sh (creates or updates the comment found by a marker line, since github.token's author is shared with every other action), and action/regen.sh (baseline, changelog against the latest tag, spec gen, changed output).
  • Minting and landing run only when regen.sh reports a change, so a no-op push never asks the broker for a token.
  • Pinned the rustdoc-toolchain default to the nightly the release was tested with; the Makefile now reads that pin from action.yml so there is one place to bump.
  • The adopter snippet grants contents: read, pull-requests: write, and id-token: write; nothing in the step needs contents write, the push goes through the bot token.
  • Rewrote the README "In CI" section around the one-step snippet and added docs/ci.md (setup, the environment's deployment side effects and the two-job alternative, what happens on each event, why the environment, every input, action-ref versus CLI-version coupling, bring-your-own-token, self-hosted runners, the reusable gate workflow). Added ci.md to the site nav after gating.md.

Closes #158. Closes #14.

Why

Adopting soothfast should cost what codecov costs: one line in CI and one App install. Until now the bot pattern needed the App private key, which only the App owner has, so the changelog and spec flows could not leave this repo. With the broker in place, the missing piece was a single action that does the right thing on both events without a second workflow file. #14 asked for a token broker so the key never leaves one place; that is what bot/ is, so it closes here rather than as a separate service design.

How was this tested

  • shellcheck 0.11.0 on every script in action/: clean.
  • bash -n on every script.
  • cargo metadata auto-detection checked against this workspace: it lists the seven self-bench crates.
  • Not run end to end: a composite action can only be exercised from a consuming repository. First real run is pointing finance-query at this commit; the pull-request path posts a gate comment, the push path lands bot/soothfast-update authored by soothfast-bot.
  • make check and make gate were not run for this layer. It changes no Rust.

Checklist

  • make check passes (fmt, clippy -D warnings, cargo test --workspace)
  • make gate BASE=master passes, or any intentional cost change is explained above
  • Tests added/updated for new behavior (not required for docs/CI-only changes)
  • Public API items have /// doc comments
  • No new dependency, or its justification is included above (see Dependency Policy)
  • Docs (README.md, docs/, soothfast:bind/soothfast:claim markers) updated if behavior changed

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

soothfast gate

gate: measuring merge-base of origin/feat/soothfast-bot-workflows in worktree (interleaved rounds)
gate: bench binaries identical (code and data match) — no measurable change possible
gate: gating backend = walltime
gate: build=08f03a noise_floor=0.26% thresholds: instructions +5% ir +5% walltime +10.0% alloc/size +5% polls/wakes +5%
ok    soothfast_spec::bench_openapi_diff walltime_median_ns 4877632.0 -> 4877632.0 (+0.0%)
ok    soothfast_spec::bench_openapi_diff allocs 77722 -> 77722 (allowed <= 81608)
ok    soothfast_spec::bench_openapi_diff alloc_bytes 10815487 -> 10815487 (allowed <= 11356261)
ok    soothfast_spec::bench_openapi_document walltime_median_ns 1741109.0 -> 1741109.0 (+0.0%)
ok    soothfast_spec::bench_openapi_document allocs 30152 -> 30152 (allowed <= 31659)
ok    soothfast_spec::bench_openapi_document alloc_bytes 4715065 -> 4715065 (allowed <= 4950818)
ok    soothfast_spec::bench_serialize_yaml walltime_median_ns 5770347.0 -> 5770347.0 (+0.0%)
ok    soothfast_spec::bench_serialize_yaml allocs 74277 -> 74277 (allowed <= 77990)
ok    soothfast_spec::bench_serialize_yaml alloc_bytes 8533615 -> 8533615 (allowed <= 8960295)
ok    soothfast_spec::bench_openapi_diff assert complexity: claimed O(n); growth drift x1.68 over sizes [16, 64, 256] (limit x2.5)
ok    soothfast_spec::bench_openapi_document assert complexity: claimed O(n); growth drift x1.55 over sizes [16, 64, 256] (limit x2.5)
ok    soothfast_spec::bench_serialize_yaml assert complexity: claimed O(n); growth drift x1.19 over sizes [16, 64, 256] (limit x2.5)
gate: passed (3 item(s))

@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch from 0086556 to 9914445 Compare September 6, 2026 00:53
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

soothfast gate

gate: measuring merge-base of origin/master in worktree (interleaved rounds)
gate: bench binaries identical (code and data match) — no measurable change possible
gate: gating backend = walltime
gate: build=08f03a noise_floor=0.24% thresholds: instructions +5% ir +5% walltime +10.0% alloc/size +5% polls/wakes +5%
ok    soothfast_sdk::bench_emit_typescript walltime_median_ns 2134783.0 -> 2134783.0 (+0.0%)
ok    soothfast_sdk::bench_emit_typescript allocs 42930 -> 42930 (allowed <= 45076)
ok    soothfast_sdk::bench_emit_typescript alloc_bytes 2938940 -> 2938940 (allowed <= 3085887)
ok    soothfast_sdk::bench_lower walltime_median_ns 1408196.0 -> 1408196.0 (+0.0%)
ok    soothfast_sdk::bench_lower allocs 23907 -> 23907 (allowed <= 25102)
ok    soothfast_sdk::bench_lower alloc_bytes 2064788 -> 2064788 (allowed <= 2168027)
ok    soothfast_sdk::bench_emit_typescript assert complexity: claimed O(n); growth drift x1.02 over sizes [16, 64, 256] (limit x2.5)
ok    soothfast_sdk::bench_lower assert complexity: claimed O(n); growth drift x1.33 over sizes [16, 64, 256] (limit x2.5)
gate: passed (2 item(s))

@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch from 9914445 to 9fd1434 Compare September 6, 2026 00:55
@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch from 9fd1434 to 4b03441 Compare September 6, 2026 01:34
@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch from 4b03441 to 908049d Compare September 6, 2026 01:35
@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch from 908049d to 5923edb Compare September 6, 2026 02:01
@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch 2 times, most recently from c849536 to 7d4b2a1 Compare September 6, 2026 02:04
@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch from 7d4b2a1 to 4cd40e1 Compare September 6, 2026 02:10
- The root action grows from installing the CLI to being the whole
  adopter surface: gate and comment on pull requests, regenerate and
  land as soothfast-bot on the default branch, one `uses:` line.
- The same scripts back this repo's own workflows, so the adopter path
  is exercised on every merge here.
- A no-op regeneration never mints a token.

Closes #158
Closes #14
@Verdenroz
Verdenroz force-pushed the feat/soothfast-action branch from 4cd40e1 to 68dbfda Compare September 6, 2026 02:12
@Verdenroz
Verdenroz merged commit b2896e3 into master Sep 6, 2026
21 checks passed
@Verdenroz
Verdenroz deleted the feat/soothfast-action branch September 6, 2026 02:36
Verdenroz added a commit that referenced this pull request Sep 6, 2026
## What changed

Three writes were still attributed to github-actions after #164: the
gate comment on pull requests, the gh-pages push in `deploy-docs`, and
`gh release create`. All three now carry the soothfast-bot identity.
Pull request jobs never receive a token for it: the broker posts the
gate comment itself. Release creation moves to its own job because a job
carries one environment and `publish` already needs the `release`
approval gate.

- Added `POST /comment` to the broker. A `pull_request` run from the
repository itself, in the `soothfast-bot` environment, sends
`{pull_request, marker, body}` over its OIDC identity; the broker checks
the number against the run's own merge ref, mints a `pull_requests:
write` token for itself, creates or updates the comment that starts with
the marker, and revokes the token. The job holds nothing. `/token`
refuses `pull_request` runs outright.
- Accepted tag refs for a landing token only when the compare of default
branch to the OIDC `sha` reports `behind` or `identical`. The commit
that ran is what is judged, never the tag name, so moving a tag after
the run starts changes nothing. Revoked on any other status.
- `action/oidc.sh` holds the broker URL and OIDC fetch shared by
`bot-token.sh` and `comment.sh`. `comment.sh` tries the broker first and
falls back to the caller's token; `gate.sh` runs the gate before any
comment and passes no token into it.
- `soothfast-gate.yml` runs in the `soothfast-bot` environment with
`id-token: write`, checks out this repository's `action/` at
`github.job_workflow_sha` so a cross-repo caller gets matching scripts,
installs the CLI through that checkout instead of a pinned
self-reference, and posts its comment through the broker with the same
fallback.
- `deploy-docs` pushes gh-pages with a landing token and revokes it.
`release.yml` gains a `release` job (report, changelog extraction, `gh
release create` as soothfast-bot) between `publish` and `binaries`;
`publish` drops the rustdoc pin it no longer needs.
- Gate output quoted in the comment is text the pull request's own
binaries wrote, now under a write-access author. It stays inside a code
fence and any fence sequence in it is neutralised first, so no line of
it can become top-level markdown (an @mention or a ChatOps command)
posted as soothfast-bot. The broker validates a `/comment` body before
it authenticates, so a malformed request costs no mint.
- `docs/ci.md` and `CLAUDE.md` describe the flow. The App's permission
list is unchanged.

Part of #158.

## Why

Harvey's requirement is that soothfast-bot is the only identity
soothfast writes with. A pull request runs unmerged code, so it must
never hold a write credential; having the broker post the comment keeps
the identity without handing the job anything. A tag push was excluded
because a tag is not the default branch; requiring the tagged commit to
already be on it makes the release equivalent to a `workflow_dispatch`
on master. The same `environment` opt-in gates every request.

## How was this tested

- `npm test`: 64 passed, 0 failed. New coverage: `/comment` creates or
updates by marker and ignores other bots' comments, refuses a different
pull request number, validates the body and size, revokes its token on
every path; `/token` refuses `pull_request`; a tag is judged by `sha`
(the fake asserts the compared head); `pull_request_target`,
`issue_comment`, and head refs stay refused.
- `tsc --noEmit`: clean. `shellcheck -x` on all eight scripts: clean.
`zizmor` 1.29.0 on every workflow and `action.yml`: no findings.
- `bot-token.sh` fails loudly with no OIDC token; `comment.sh` falls
back to the job token when the broker is unreachable (the case CI is in
until the Worker is deployed).
- Not run end to end. The first exercise is this PR's own gate matrix
once the broker is live (the comment should appear as soothfast-bot);
the release path runs at the next tag.
- `make check` and `make gate` were not run for this layer. It changes
no Rust.

Manual, before the first run: this repository's `soothfast-bot`
environment must have no deployment branch policy, since the gate now
uses it on pull requests and the release on tags.

## Checklist

- [ ] `make check` passes (fmt, clippy `-D warnings`, `cargo test
--workspace`)
- [ ] `make gate BASE=master` passes, or any intentional cost change is
explained above
- [x] Tests added/updated for new behavior (not required for
docs/CI-only changes)
- [ ] Public API items have `///` doc comments
- [x] No new dependency, or its justification is included above (see
[Dependency Policy](../CONTRIBUTING.md#dependency-policy))
- [x] Docs (`README.md`, `docs/`, `soothfast:bind`/`soothfast:claim`
markers) updated if behavior changed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

changelog.yml onboarding requires a GitHub App private key Public soothfast-bot: OIDC token-broker service instead of shared App credentials

1 participant