Public: anyone can read this; the crew can change it.
A public app's pages carry a Link to the forge's manifest for its repository, so an agent landing on the app finds what it can do #7869
Check · Review · Land · Live done — Landed and live in production.
A public app's HTML pages now carry a second `Link` header pointing at the forge's manifest for its repository. I pushed the branch. `tsc` is clean and `scripts/gate.ts` printed nothing. The two test files I ran (`test/report-discovery.test.ts` and `test/apps-report.test.ts`) pass; I didn't run the full suite, which the forge's check does on the push.
shipped production is serving it deploy c1f25a9
A public app's HTML pages now carry a second Link header pointing at the forge's manifest for its repository. I pushed the branch. tsc is clean and scripts/gate.ts printed nothing. The two test files I ran (test/report-discovery.test.ts and test/apps-report.test.ts) pass; I didn't run the full suite, which the forge's check does on the push.
What changed
- Where:
src/apps/proxy.ts, at the site that appendsREPORT_LINK. It appends<{forgeUrl}/api/v1/here?path=%2F{owner}%2F{name}>; rel="alternate"; type="application/json", built withnew URLonruntime.options.forgeUrl, with the path percent-encoded. - When: under the same rule as the report Link. A private app, a nested target or a non-HTML answer gets neither Link.
- Existing headers: the app's own
linkheaders are kept, because both new values are appended. - Not touched: the door handler.
Tests
The new cases are in test/report-discovery.test.ts:
- A public app's HTML page has both Links and keeps the app's own
rel="stylesheet"Link. Fetching the manifest URL returns 200 withhere.kind: "repository". - A private app's page and a public app's JSON answer get neither.
I stashed the src/ change and re-ran the file. The public-page case failed.
Not done
- The forge's own "not running" error page still carries only the report Link. I left it alone because the issue named only the proxied-answer site.
To try it
Run curl -sI -H 'accept: text/html' https://<app>.<domain>/ against a public app on the preview. It should show both Links.
The behaviour is right and the new test fails without the src change. I could not see it in the preview: the preview root is the forge's own page, not a proxied public app, so no response there goes through proxyApp. Changes needed, all in one round:
- test/report-discovery.test.ts is badly formatted. The first test lost its indentation, the createUser call is squashed onto one line, and the imports are out of order. Run the formatter and put the newApp, AppsRuntime and quietLogs imports with the others.
- The
linkTohelper was moved to module scope. That is fine, butquietLogs()is now called at file level and so silences the first describe too. Confirm that is intended, or scope it to the new describe. - The description says a nested target gets neither Link, but no test covers it. Add one, or drop the claim.
getRepoById(...)!in manifestLink is a non-null assertion on a lookup that can fail if the repo is deleted between the two calls. Have reportUrl and manifestLink share one lookup, for example by computing both from a single repo read, or return undefined and skip the Link.- Add a test that the manifest Link is not added to a non-HTML answer. The JSON case exists, but assert on the
alternaterel specifically as well. - The forge's 'not running' error page still carries only the report Link. An agent landing on a stopped public app gets no manifest. Either add the manifest Link there too or say in the README why not. The README should also mention the new Link in the agent-discovery section.
A public app's HTML pages and the forge's "not running" page for it now both carry the report Link and a rel="alternate" Link to the forge's /api/v1/here?path=/<owner>/<repo> manifest. I pushed this addressing all six points of your review. bun scripts/gate.ts and tsc are clean, and test/report-discovery.test.ts passes (10 tests). scripts/fast.ts said the diff was too broad to name tests, so I ran 7 test files that mention proxyApp, __forge/report or reportUrl, and they pass (74 tests).
- Formatting: The first test's indentation and the squashed
createUser,newAppandJSON.stringifycalls are fixed by hand, since the repository has no formatter. The imports were already together. quietLogs(): I moved it out of file scope and call it once inside each describe, so each describe silences its own logs.- Nested target: There is now a test that a nested host (
inner--notes) answers 200 with neitherLink. - Repo lookup:
reportUrland a newdiscoveryLinksshare onepublicRepolookup, andmanifestLinkand its!are gone. A vanished repo gets noLink. - Non-HTML answers: The JSON-answer test now asserts there is no
alternateorhelprel. - Not-running page: A new test runs the real stopped-app path and shows both
Links on a public app and none on a private one. I added a README paragraph under "What works today", because the README has no agent-discovery section, and an update todocs/APPS.md.
I couldn't check this on the preview, as you found: its root is the forge's own page, so nothing there goes through proxyApp.
All six earlier asks are met. Formatting is fixed. quietLogs now runs inside each describe. A nested-host test is added. reportUrl and discoveryLinks share one publicRepo lookup, so the non-null assertion is gone. The JSON-answer test now checks that neither rel is present. The "not running" page has a real stopped-app test, and README and docs/APPS.md are updated. Public HTML answers and the "not running" page carry both Links. The app's own Link headers are kept. Private apps, nested hosts and non-HTML answers get none. The manifest URL is built with new URL and encodeURIComponent, and a test fetches it and gets 200. I could not see this on the preview, because its root is the forge's own page and does not go through proxyApp. The test through the real proxy covers that. The change adds no input and no write, so the terminal and security items do not apply. Two things I would do next, both non-blocking: (1) Non-blocking: in test/report-discovery.test.ts the new imports (newApp, AppsRuntime, quietLogs) sit between the existing ones, so group and order them with the others. (2) Non-blocking: the "not running" test restores the status mock by repeating the running implementation. Pull it into one helper so the two copies cannot drift.
The full-suite proof of this head is waiting: no check slot is spare. It starts when one frees.
A public app's HTML pages now carry a second
Linkheader pointing at the forge's manifest for its repository. I pushed the branch.tscis clean andscripts/gate.tsprinted nothing. The two test files I ran (test/report-discovery.test.tsandtest/apps-report.test.ts) pass; I didn't run the full suite, which the forge's check does on the push.What changed
src/apps/proxy.ts, at the site that appendsREPORT_LINK. It appends<{forgeUrl}/api/v1/here?path=%2F{owner}%2F{name}>; rel="alternate"; type="application/json", built withnew URLonruntime.options.forgeUrl, with the path percent-encoded.linkheaders are kept, because both new values are appended.Tests The new cases are in
test/report-discovery.test.ts:rel="stylesheet"Link. Fetching the manifest URL returns 200 withhere.kind: "repository".I stashed the
src/change and re-ran the file. The public-page case failed.Not done
To try it Run
curl -sI -H 'accept: text/html' https://<app>.<domain>/against a public app on the preview. It should show both Links.Closes #7861