Public: anyone can read this; the crew can change it.

Private by default is plain: every repository and app page says in one line who can see it, and the API gives the same line #10394

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Most of #10391 was already on main. The `audience` sentence was already in `GET /api/v1/repos/:owner/:name`, the MCP `repo` tool, the repository head and the app page, and a visibility change was already recorded by the `repo.settings_updated` event. This pull fills what was missing. It ships as `audience.who`, not as a new `visibility` object as the issue asked; the reason is below.

shipped production is serving it deploy f21b126

opened this

Most of #10391 was already on main. The audience sentence was already in GET /api/v1/repos/:owner/:name, the MCP repo tool, the repository head and the app page, and a visibility change was already recorded by the repo.settings_updated event. This pull fills what was missing. It ships as audience.who, not as a new visibility object as the issue asked; the reason is below.

What changed

  • src/core/audience.ts: a pure whoOf(facts) gives one word for who can read the repository: owner (its owner and their crew), members (a private repository owned by an organization) or anyone (public). audienceFor now returns { level, who, sentence }, so the API, the MCP repo tool and the terminal all get who from the same call. audienceOf is unchanged, so its existing tests still hold.
  • PATCH /api/v1/repos/:owner/:name now includes audience in its response, so the line changes in the same answer as the visibility change.
  • The MCP here tool repeats the line on a repository page (here.audience, in src/actions/here.ts) and on an app or preview link (audience, in src/apps/here.ts). The tool docs and docs/TOUR.md say so.
  • New test/visibility-line.test.ts covers:

- a new repository is private with who: "owner"; - a fork of a private repository stays private even when public is asked for; - a new app and its repository are private; - the preview of a private app's pull shows its owner the line, and refuses a stranger the same way as a missing host; - a stranger asking about a private repository, through the API (with a token and signed out) or the MCP here tool, gets exactly what a missing repository gets; - a visibility change PATCH is receipted and returns the new line in the same response; - whoOf covers all four cases.

What I chose not to do

  • No visibility object in the JSON. visibility is already the "public" | "private" string that the PATCH input and many clients and tests read. Turning it into { who, why } would break them. who sits beside the existing sentence, which already does the job of why.
  • No signed-in value. No rule on the forge lets every signed-in person read a repository, so that value could never appear.
  • No page changes. The sentence was already shown once in the repository head's meta line, including on the app's page, so nothing under src/web/ changed and docs/design/attention-budget.json is untouched.

Checks run: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts named too many files to run, so I picked the covering tests myself: visibility-line, audience, audience-surfaces, repo-audience, app-audience, mcp-here, here-manifest, here-timing, mcp, mcp-size, api-repo-timing, apps-presence, mcp-card and standards. All 163 pass.

Try it: GET /api/v1/repos/<you>/<a private repo> shows audience.who: "owner". PATCH it with {"visibility":"public"} and the same response shows "anyone" with the new sentence. MCP here with that repository's URL shows here.audience.

🤖 Generated with Claude Code

Closes #10391

commented

Most of #10391 was already on main. The audience sentence was already in GET /api/v1/repos/:owner/:name, the MCP repo tool, the repository head and the app page, and a visibility change was already recorded by the repo.settings_updated event. This pull fills what was missing. It ships as audience.who, not as a new visibility object as the issue asked; the reason is below.

What changed

  • src/core/audience.ts: a pure whoOf(facts) gives one word for who can read the repository: owner (its owner and their crew), members (a private repository owned by an organization) or anyone (public). audienceFor now returns { level, who, sentence }, so the API, the MCP repo tool and the terminal all get who from the same call. audienceOf is unchanged, so its existing tests still hold.
  • PATCH /api/v1/repos/:owner/:name now includes audience in its response, so the line changes in the same answer as the visibility change.
  • The MCP here tool repeats the line on a repository page (here.audience, in src/actions/here.ts) and on an app or preview link (audience, in src/apps/here.ts). The tool docs and docs/TOUR.md say so.
  • New test/visibility-line.test.ts covers:

- a new repository is private with who: "owner"; - a fork of a private repository stays private even when public is asked for; - a new app and its repository are private; - the preview of a private app's pull shows its owner the line, and refuses a stranger the same way as a missing host; - a stranger asking about a private repository, through the API (with a token and signed out) or the MCP here tool, gets exactly what a missing repository gets; - a visibility change PATCH is receipted and returns the new line in the same response; - whoOf covers all four cases.

What I chose not to do

  • No visibility object in the JSON. visibility is already the "public" | "private" string that the PATCH input and many clients and tests read. Turning it into { who, why } would break them. who sits beside the existing sentence, which already does the job of why.
  • No signed-in value. No rule on the forge lets every signed-in person read a repository, so that value could never appear.
  • No page changes. The sentence was already shown once in the repository head's meta line, including on the app's page, so nothing under src/web/ changed and docs/design/attention-budget.json is untouched.

Checks run: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts named too many files to run, so I picked the covering tests myself: visibility-line, audience, audience-surfaces, repo-audience, app-audience, mcp-here, here-manifest, here-timing, mcp, mcp-size, api-repo-timing, apps-presence, mcp-card and standards. All 163 pass.

Try it: GET /api/v1/repos/<you>/<a private repo> shows audience.who: "owner". PATCH it with {"visibility":"public"} and the same response shows "anyone" with the new sentence. MCP here with that repository's URL shows here.audience.

🤖 Generated with Claude Code

Changes requested — before the latest push

Changes needed (first round):

  1. SECURITY (high, fix first): src/apps/here.ts now returns audienceFor(repo, user) to anyone who canEnterApp. A public app can sit on a private repository, so a stranger or non-member who may open the app is told the repository is private and given its collaborator counts. That is the leak the issue's box 5 forbids. Include audience only when the viewer may read the repository (the same gate repoHere uses), otherwise omit it. Add a test: a public app on a private repo, asked by a non-member, carries no audience.
  2. The issue asked for visibility {who, why} on the repository GET and on the MCP here tool. The pull ships audience.who instead and says so. If that is the decision, the issue's boxes 1 and 4 need their text amended; otherwise add the visibility field alongside the existing string (e.g. visibility_line) rather than skipping it. Also box 3: add an assertion on the sentence/who for the new app and the preview of a private pull, not only on the stored visibility.
  3. Docs: TOUR.md is edited for the JSON shape, but there is no docs/features/ page note for the app-link audience, and nothing shows that the terminal exposes who. Say in the pull how the terminal door reaches it, or add it.
commented

A repository's GET and PATCH answers, and the MCP repo and here tools, now include audience: { level, who, sentence }. who is one word a client can branch on: owner, members or anyone. This round fixes the leak the review found: the here tool used on an app's link now includes audience only when the viewer may read the repository (canRead). A public app can run on a private repository. Before this fix, anyone who could open the app was told the repository is private and given its collaborator counts. Now those visitors get the rest of the answer with no audience key.

Review points:

  • Leak (src/apps/here.ts): audience is only added when canRead passes. A new test sets up a public app on a private repository and asks as a non-member: the answer has no audience, while the owner still gets it. I checked that the test fails without the fix.
  • visibility vs audience: I kept audience and did not add visibility as an object. visibility is already the "public" | "private" string that clients read, and changing its type would break them. A second field like visibility_line would repeat what audience already says. There is also no signed-in-only level to report. I tried to amend the issue's box 1 to describe this, but my token lacks the scope (forbidden), so someone with write access needs to edit that line. Box 4 (the visibility change is receipted and the line changes in the same answer) is met as written and needs no edit.
  • Box 3: the new app and the preview of a private pull are now asserted on the line itself (who, level, the sentence starting "Private:", and equality with audienceFor), through here on the app's link. Before, only the stored setting was checked.
  • Docs and terminal: new docs/features/visibility-line.md lists every place the line is shown: web, API, MCP (including the gate on app links) and terminal. docs/TOUR.md links to it. The terminal's About card (I) prints the sentence, which already opens with who can see the repository. It doesn't print who as a separate word, because people read the terminal and nothing branches on it.

Checks: scripts/gate.ts and tsc --noEmit are clean. scripts/fast.ts named 1,646 files, which is over its time budget, so I ran test/visibility-line.test.ts plus the 19 test files that cover the here tool, app links, the MCP docs and docs/features: 256 pass, 0 fail.

To try it: call the MCP here tool with the link of a public app whose repository is private, once as a non-member and once as the owner. Only the owner's answer has audience.

🤖 Generated with Claude Code

Approved

The earlier leak is fixed: on an app link, audience appears only when canRead passes, and a test covers a public app on a private repository (the visitor gets no audience, the owner does). Box 3 is now asserted on the line itself. The docs are in docs/features/visibility-line.md, with no README change. The terminal About card already prints the sentence. I did not run the tests here. The preview has no repository to query, so I could not try the line there.

Non-blocking: box 1 asks for a visibility object. The pull ships audience.who and keeps visibility as the existing string, for a reason that holds. Someone with write access should reword the box. The signed-in value does not exist on this forge.

commented

Left #10391 open: 1 unticked item remain (A repository's GET JSON carries visibility ({who: owner | members | signed-in | anyone, why: one sentence}); the MCP here tool repeats it.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs