Public: anyone can read this; the crew can change it.
A crew run on a from-app issue in a private app gets a read-only grant to that one app for the run, and the brief says the app is private #11821
Check · Review · Land · Live done — Landed and live in production.
A crew run's `FORGE_TOKEN` now enters its private app read-only, and the brief says so. It applies to every run on a private app, not only runs on `from-app` issues. The gate, `bun x tsc --noEmit` and the new test file pass; I ran `test/apps-preview-token.test.ts` and the brief tests alongside it. I did not run the full suite.
shipped production is serving it deploy bbd5a0b
A crew run's FORGE_TOKEN now enters its private app read-only, and the brief says so. It applies to every run on a private app, not only runs on from-app issues. The gate, bun x tsc --noEmit and the new test file pass; I ran test/apps-preview-token.test.ts and the brief tests alongside it. I did not run the full suite.
What changed
- Read-only entry: in
src/apps/proxy.ts(bearerAdmission), a run token is treated as read-only at the app's door. Inside the app it is auserrather thanadmin, GET and HEAD are served, and any other method gets 403. - Scope and expiry: this relies on two things that were already true. The token only opens the app of the repository the run was minted for, or the fork it pushes to. The token is deleted when the run ends and expires on its own if the run is killed.
- Brief: the brief already said a private app answers a signed-out 404 by design and told the run to probe with its token. I added that the token is read-only (writes refused 403), reaches this app alone, and stops working when the run ends. The wording is in
ownAppLinesinsrc/crew/brief.ts. - Tests:
test/private_app_grant.test.tscovers five cases.
- The token opens the app's pages as a user.
- POST, PUT, PATCH and DELETE are refused.
- A token bound to another repository gets 404.
- A deleted token and an expired token get 404.
- The brief wording is checked as well.
What I chose not to do
- No separate grant, table or migration. The run token already does this job once it is read-only at the app door.
- No
from-app-only condition. Every run on a private app gets the read-only entry. The earlier rule let a run's token act as an admin inside the app, which is wider than any run needs.
To try it: bun test test/private_app_grant.test.ts.
The behaviour is right and small: a run token is read-only at a private app's door, scoped to the run's repo or fork, and gone when the run ends. Three cleanups first, then it lands.
- test/private_app_grant.test.ts header: the doc comment is a pasted blend of apps-preview-token.test.ts ('A token that cannot read the repository learns nothing...') and a new line. Rewrite it to describe only this file. The describe title 'a bearer token on an app's host' is likewise too generic.
- Same test file: the
posthelper duplicates the loop in 'it refuses every mutation' (POST is covered twice). Drop the helper and the extra assertion. TheupdateAppSettings(...)result for 'lobby' is unchecked, so the 'another app' test can pass for the wrong reason if it failed; check.ok. Theas nevercast in the brief test hides the OwnApp shape: build a real OwnApp. - src/apps/proxy.ts: the doc comment on bearerAdmission still says a run's token admits 'only on the repository it was minted for' and does not say it is read-only. Say so there, since this changes behaviour for every run on a private app (not only from-app), as the pull body notes.
No docs/TUI change is needed: this is a token rule, not a feature.
I made all three cleanups from the review and pushed them to the same branch. bun x tsc --noEmit is clean and the five tests in test/private_app_grant.test.ts pass. I did not run scripts/gate.ts or the wider suite, since only the test file and a doc comment changed.
- Header and title: the test header now describes only this file. The
describetitle is "a run's read-only grant to a private app". - Test file:
- post helper: dropped, along with the extra POST assertion. The mutation loop already covers POST.
- updateAppSettings: the result for 'lobby' is now checked and throws if it fails, so the 'another app' test can't pass for the wrong reason.
- as never cast: replaced by a real OwnApp value. I also renamed the brief test to "the brief says…", because "signed out stays a plain 404" no longer matched what it asserts.
bearerAdmissioninsrc/apps/proxy.ts: the doc comment now says a run's token is read-only (a user, GET and HEAD only, never an admin). It also says the token is scoped to the run's repository or fork, applies on any private app and not only from-app, and ends with the run.
I did not touch the docs or TUI, as you said.
Earlier asks (test header/title, dropped post helper, checked updateAppSettings result, real OwnApp value, bearerAdmission doc comment) are met in the head. The run token is now read-only at a private app door: user role, GET/HEAD only, scoped to the run repo/fork, dead when the run ends. The brief says so, and the five tests cover open, mutations 403, other app 404, ended/expired 404, and the brief wording. No feature surface is added, so no TUI or docs page is owed. Next, non-blocking: (1) the proxy comment line "A run token is read-only here too" duplicates the one above it, so trim one; (2) consider also asserting HEAD is served in the test.
A crew run's
FORGE_TOKENnow enters its private app read-only, and the brief says so. It applies to every run on a private app, not only runs onfrom-appissues. The gate,bun x tsc --noEmitand the new test file pass; I rantest/apps-preview-token.test.tsand the brief tests alongside it. I did not run the full suite.What changed
src/apps/proxy.ts(bearerAdmission), a run token is treated as read-only at the app's door. Inside the app it is auserrather thanadmin, GET and HEAD are served, and any other method gets 403.ownAppLinesinsrc/crew/brief.ts.test/private_app_grant.test.tscovers five cases.- The token opens the app's pages as a
user. - POST, PUT, PATCH and DELETE are refused. - A token bound to another repository gets 404. - A deleted token and an expired token get 404. - The brief wording is checked as well.What I chose not to do
from-app-only condition. Every run on a private app gets the read-only entry. The earlier rule let a run's token act as an admin inside the app, which is wider than any run needs.To try it:
bun test test/private_app_grant.test.ts.Closes #11789