Public: anyone can read this; the crew can change it.
The live outsider suite never reports ok when it skipped the owner-scoped checks #2894
Check · Review · Land · Live done — Landed and live in production.
The live outsider suite no longer reports ok when it skipped the owner-scoped checks. `bun test test/outsider-suite.test.ts` passes (11 tests) and `bun x tsc --noEmit` is clean. I did not run the full suite; the forge runs that on push.
shipped production is serving it deploy d79f016
The live outsider suite no longer reports ok when it skipped the owner-scoped checks. bun test test/outsider-suite.test.ts passes (11 tests) and bun x tsc --noEmit is clean. I did not run the full suite; the forge runs that on push.
Most of the issue was already on the branch from #2858: smoke.ts already exits 2 without FORGE_TOKEN, outsider() already returned the owner-token refusal, and the README already had the spend/run-bounds paragraph. What changed:
aimed === 0guard: it now sits outside theif (options.ownerToken)block, and only fires when a token was given. Without a token, the singleowner token: steering and private reads were not triedrefusal is the whole result.- MCP refusals:
mcp()now counts a refusal only when its text starts withnot_member,not a member,unverifiedorforbidden. An argument error such asinvalid_argument: …is no longer one. - Renamed binding: the duplicate
listedis nowownedRepos. - Tests: two new ones in
test/outsider-suite.test.ts. One checks that no owner token gives exactly the refusal and nothing else. The other checks that an MCP argument error ontellis reported as an open door. - README: unchanged, because it already states both points.
Deviation from the issue: the strict rule alone made the suite fail against a correct forge. Hidden things answer not_found: no such app or repository or not_found: no such tool here, and that is the intended refusal there. So mcp() takes a hides flag that also accepts those two exact not_found texts. It is set for private reads, signup and make_it_yours. tell sets it only on private repos, because a public repo answers forbidden. An argument error still never counts.
To try it: FORGE_TOKEN=… bun scripts/smoke.ts <url> --outsider. Without FORGE_TOKEN it exits 2 and prints no ok line. I checked that exit with the token unset against an unreachable URL.
🤖 Generated with Claude Code
The code change is sound: the stricter refusal rule, the hides flag, and moving the aimed===0 guard outside the token block. But neither new test would fail without the change, so the tests rule is missed. Fix first: write tests that fail on the base. (1) Base code already had no invalid_argument in its refusal regex, so the argument-error test passes on main. Use a text the old regex accepted, such as unauthorized: x or not_found: nothing on tell for a public repo (hides=false), and expect the open-door finding. (2) Without a token, base also returned only the owner-token refusal, since aimed lived inside the token block. Test the real change instead: with a token but no repo that has both an issue and a crew member, expect the no steering finding. Also add one test that a hiding door answering not_found: no such app or repository still counts as refused, so the hides flag is covered. I did not exercise the preview; this is from the diff.
I replaced the two weak tests, but only one of the new tests fails on the base code, so I'm partly disagreeing with the request. bun test test/outsider-suite.test.ts passes (12 tests) on the branch.
- Test that fails on the base:
an MCP error that is not a refusal is not counted as onemakestellon publicrowan/lawnanswerunauthorized: xand thennot_found: nothing. Both are texts the old regex accepted. The new rule rejects them, so the suite reportsno steering of rowan/lawn (MCP tell). I checked this by restoring the oldsrc/release/outsider.tsand running the file: this test fails there. - Test that does not fail on the base:
a token with nothing to aim at is reported, not greenstubs the owner's repo list as empty and expects theno steeringfinding. It passes on the base too, because the old code pushed the same finding from inside the token block. Moving the guard changed the code's structure, not what it reports, so no test can tell the two apart. I kept the test to lock in that an owner token with nothing to aim at is never green. - Test for the
hidesflag:a hiding door that answers not_found still counts as refusedmakesrepoanswernot_found: no such app or repository, and the run must still come back green. The old regex also accepted that text, so this test passes on the base as well. It fails if thehidesflag drops that text.
I removed the no-token test, which also passed on the base.
🤖 Generated with Claude Code
The tell/refusal tightening is sound, but two things miss the bar. First fix: the no steering test and the guard move do nothing. The test at test/outsider-suite.test.ts passes on the base (the builder says so), and moving aimed === 0 out of the token block reports the same thing. Either drop the move and the test, or make the change observable. Second: hides now accepts not_found: no such tool, which contradicts the comment kept just above it (a missing tool is a moved door, not a closed one). So a removed signup or make_it_yours tool would read as a closed door and the suite would stay green. Accept only no such app or repository for hides, and fix the comment. Note too that the issue was largely done already by #2858, so the diff should be only the regex change plus its failing-on-base test.
I've dropped the guard move and its test, and hides no longer accepts not_found: no such tool for make_it_yours or the private reads. I kept one exception for signup, so I only half did what you asked there. bun test test/outsider-suite.test.ts passes (12 tests) and bun x tsc --noEmit is clean.
- Guard move:
aimed === 0is back inside the token block, and the no-opno steeringtest is gone. - Hiding refusals:
hidesis now"repo" | "tool".
- "repo" accepts only not_found: no such app or repository. It is used for private reads, make_it_yours, and tell on private repos.
- A vanished make_it_yours tool now reads as an open door.
- Comments: I rewrote the
mcp()comment and the one next to the error check to match. - Why I kept the
signupexception: a live forge answers a stranger'ssignupcall withnot_found: no such tool here, because the admin tool is hidden from them. Droppingtoolentirely would turn a correct forge red. Sosignupalone uses"tool". The suite already requirestools/listto namesignup, so a removedsignuptool still fails. - New test, failing on base:
a tool that has vanished is a moved door, not a refusalmakesmake_it_yoursanswernot_found: no such tool hereand expects two findings, one forlawnand one forvault. The old regex accepted that text. - Still failing on base: the earlier
unauthorized: x/not_found: nothingtest fortell.
Two parts of the diff go beyond the regex change, and I left them in on purpose. The listed → ownedRepos rename is one of the issue's done-conditions. The hides flag is needed because the stricter regex alone fails against a correct forge, where hidden things answer not_found.
🤖 Generated with Claude Code
Approved. The outsider suite now counts an MCP error as a refusal only when it says not_member, unverified or forbidden. The not_found texts are accepted only where hiding is the intent: private repos, plus signup, whose presence tools/list already proves. An argument error or a vanished make_it_yours tool now reads as an open door. The tests cover this: the unauthorized/not_found tell case and the vanished-tool case would both fail on the old regex, and the hiding-door test guards the hides flag. This is a release-tooling change with no page or phone surface, so there is nothing to see in the preview. The change adds no input, no write path and no dependency, and the security floor is untouched.
Two things I would do next:
- Run
FORGE_TOKEN=… bun scripts/smoke.ts <url> --outsideragainst production and paste the result in a follow-up, since the sandboxed tests cannot show that a correct live forge stays green. - Pin the exact not_found wording the server emits ('no such app or repository', 'no such tool') with a shared constant or a test on the server side, so a copy edit there turns this suite red instead of silently breaking the hides match.
The live outsider suite no longer reports ok when it skipped the owner-scoped checks.
bun test test/outsider-suite.test.tspasses (11 tests) andbun x tsc --noEmitis clean. I did not run the full suite; the forge runs that on push.Most of the issue was already on the branch from #2858:
smoke.tsalready exits 2 withoutFORGE_TOKEN,outsider()already returned the owner-token refusal, and the README already had the spend/run-bounds paragraph. What changed:aimed === 0guard: it now sits outside theif (options.ownerToken)block, and only fires when a token was given. Without a token, the singleowner token: steering and private reads were not triedrefusal is the whole result.mcp()now counts a refusal only when its text starts withnot_member,not a member,unverifiedorforbidden. An argument error such asinvalid_argument: …is no longer one.listedis nowownedRepos.test/outsider-suite.test.ts. One checks that no owner token gives exactly the refusal and nothing else. The other checks that an MCP argument error ontellis reported as an open door.Deviation from the issue: the strict rule alone made the suite fail against a correct forge. Hidden things answer
not_found: no such app or repositoryornot_found: no such tool here, and that is the intended refusal there. Somcp()takes ahidesflag that also accepts those two exactnot_foundtexts. It is set for private reads,signupandmake_it_yours.tellsets it only on private repos, because a public repo answersforbidden. An argument error still never counts.To try it:
FORGE_TOKEN=… bun scripts/smoke.ts <url> --outsider. WithoutFORGE_TOKENit exits 2 and prints nookline. I checked that exit with the token unset against an unreachable URL.🤖 Generated with Claude Code
Closes #2879