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

A preview of the forge can be looked at as its owner: looks of chosen pages are taken signed in, over seeded data #8830

Merged opened this

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

Merged

A preview of the forge can now be pictured signed in as its owner. Pages that `forge.json` lists under the new `app.looksAsOwner` get their phone and desktop pictures taken as an admin. The preview seeds a preview-only admin and a small fixture so those pages have something to show. Production refuses both.

shipped production is serving it deploy 18f765b

opened this

A preview of the forge can now be pictured signed in as its owner. Pages that forge.json lists under the new app.looksAsOwner get their phone and desktop pictures taken as an admin. The preview seeds a preview-only admin and a small fixture so those pages have something to show. Production refuses both.

What changed

  • src/apps/manifest.ts: new field app.looksAsOwner. Each entry is a page path checked like the ones in app.looks, and each must also appear in app.looks, or the manifest is refused. Leaving it out means no pages. I folded the path checks shared by looks and looksAsOwner into one pathList helper; the error messages for app.looks read the same as before.
  • src/apps/runtime.ts: the environment status now carries looksAsOwner.
  • src/looks/service.ts: for a listed page, phone and desktop are taken with the owner's admin grant and recorded as takenAs: "owner", so only writers can fetch them (mayFetchLook). Pages not listed, and every thumb, are still taken as nobody on a public app.
  • src/spawn/preview-seed.ts (new): when FORGE_APP_ENV=preview, it creates preview-admin once. The account has no password and no session. It also seeds a public repo preview-admin/sample with two issues and an open pull, three agent runs that spent money this week, a valve turned to 60% (the flow week), and two board panels pinned to home. Anywhere else it answers not_preview and creates nothing.
  • src/spawn/preview-owner.ts (new), wired into resolveAuth in src/http/server.ts and set up in src/main.ts:

- In a preview only, a request with an x-forge-identity header naming an admin, signed with FORGE_APP_IDENTITY_KEY and not expired, is treated as the preview admin. - It counts as an edge identity, so changes are still held to the same-origin check. - Production never reads the key, so the headers are ignored there. - Anyone the parent forge marks as admin of the repo is a writer, and can already push code that runs over a preview's data, so this gives them nothing new.

  • forge.json: "looksAsOwner": ["/", "/flow", "/talk"].
  • Docs: docs/features/preview-owner-looks.md (new) and a paragraph in docs/APPS.md under "The look".

What to look at The asNobody(kind, page) rule in src/looks/service.ts and the ordering in resolveAuth. Also the choice to reuse via: "edge": it gives the same-origin check without adding a new kind of sign-in. The catch is that the sign-out link in a preview points at the platform's logout.

Not done here The desk and phone looks of home and /flow that #8698 and #8726 asked for will not appear on this pull. Looks are taken by the parent forge, which runs main and ignores the new field until this merges. The first pull after merge will carry them.

How it was checked

  • bun scripts/gate.ts and bun x tsc --noEmit pass.
  • test/looks-owner.test.ts (new) passes 8 of 8, covering:

- the manifest field; - the seed refused in production and with no environment set, and running once in a preview; - a signed admin seeing /flow, while a reader, a forged signature or an expired one gets nothing; - the same-origin check on changes; - production ignoring the headers; - a page listed as owner getting the admin grant while the others stay nobody.

  • scripts/fast.ts called the diff too broad because forge.json changed, so I picked the covering suites myself: looks, forge-looks, refresh-meta, edge-auth, signin-edge, apps-adversarial, here-manifest, board-pins. 174 pass, 1 skipped, 3 fail. The 3 are the real-Chromium capture tests in test/looks.test.ts, and they fail the same way on unchanged main in this sandbox, so they are not caused by this change.
  • I booted it with FORGE_APP_ENV=preview and a signed admin identity: the log says "preview seeded", / answers 200, /flow answers 200 in 18 ms showing preview-admin/sample, home's pin slot shows both pinned panels, and an anonymous /flow is redirected to sign in.

Try it Run FORGE_APP_ENV=preview FORGE_APP_IDENTITY_KEY=k FORGE_DATA=$(mktemp -d) bun run src/main.ts. Then request /flow with x-forge-identity: u1|you|admin|<expiry ms> and x-forge-signature set to the base64url HMAC-SHA256 of that identity string under k.

🤖 Generated with Claude Code

Closes #8812

commented

A preview of the forge can now be pictured signed in as its owner. Pages that forge.json lists under the new app.looksAsOwner get their phone and desktop pictures taken as an admin. The preview seeds a preview-only admin and a small fixture so those pages have something to show. Production refuses both.

What changed

  • src/apps/manifest.ts: new field app.looksAsOwner. Each entry is a page path checked like the ones in app.looks, and each must also appear in app.looks, or the manifest is refused. Leaving it out means no pages. I folded the path checks shared by looks and looksAsOwner into one pathList helper; the error messages for app.looks read the same as before.
  • src/apps/runtime.ts: the environment status now carries looksAsOwner.
  • src/looks/service.ts: for a listed page, phone and desktop are taken with the owner's admin grant and recorded as takenAs: "owner", so only writers can fetch them (mayFetchLook). Pages not listed, and every thumb, are still taken as nobody on a public app.
  • src/spawn/preview-seed.ts (new): when FORGE_APP_ENV=preview, it creates preview-admin once. The account has no password and no session. It also seeds a public repo preview-admin/sample with two issues and an open pull, three agent runs that spent money this week, a valve turned to 60% (the flow week), and two board panels pinned to home. Anywhere else it answers not_preview and creates nothing.
  • src/spawn/preview-owner.ts (new), wired into resolveAuth in src/http/server.ts and set up in src/main.ts:

- In a preview only, a request with an x-forge-identity header naming an admin, signed with FORGE_APP_IDENTITY_KEY and not expired, is treated as the preview admin. - It counts as an edge identity, so changes are still held to the same-origin check. - Production never reads the key, so the headers are ignored there. - Anyone the parent forge marks as admin of the repo is a writer, and can already push code that runs over a preview's data, so this gives them nothing new.

  • forge.json: "looksAsOwner": ["/", "/flow", "/talk"].
  • Docs: docs/features/preview-owner-looks.md (new) and a paragraph in docs/APPS.md under "The look".

What to look at The asNobody(kind, page) rule in src/looks/service.ts and the ordering in resolveAuth. Also the choice to reuse via: "edge": it gives the same-origin check without adding a new kind of sign-in. The catch is that the sign-out link in a preview points at the platform's logout.

Not done here The desk and phone looks of home and /flow that #8698 and #8726 asked for will not appear on this pull. Looks are taken by the parent forge, which runs main and ignores the new field until this merges. The first pull after merge will carry them.

How it was checked

  • bun scripts/gate.ts and bun x tsc --noEmit pass.
  • test/looks-owner.test.ts (new) passes 8 of 8, covering:

- the manifest field; - the seed refused in production and with no environment set, and running once in a preview; - a signed admin seeing /flow, while a reader, a forged signature or an expired one gets nothing; - the same-origin check on changes; - production ignoring the headers; - a page listed as owner getting the admin grant while the others stay nobody.

  • scripts/fast.ts called the diff too broad because forge.json changed, so I picked the covering suites myself: looks, forge-looks, refresh-meta, edge-auth, signin-edge, apps-adversarial, here-manifest, board-pins. 174 pass, 1 skipped, 3 fail. The 3 are the real-Chromium capture tests in test/looks.test.ts, and they fail the same way on unchanged main in this sandbox, so they are not caused by this change.
  • I booted it with FORGE_APP_ENV=preview and a signed admin identity: the log says "preview seeded", / answers 200, /flow answers 200 in 18 ms showing preview-admin/sample, home's pin slot shows both pinned panels, and an anonymous /flow is redirected to sign in.

Try it Run FORGE_APP_ENV=preview FORGE_APP_IDENTITY_KEY=k FORGE_DATA=$(mktemp -d) bun run src/main.ts. Then request /flow with x-forge-identity: u1|you|admin|<expiry ms> and x-forge-signature set to the base64url HMAC-SHA256 of that identity string under k.

🤖 Generated with Claude Code

commented

A preview of the forge can now be pictured signed in as its owner. Pages that forge.json lists under the new app.looksAsOwner get their phone and desktop pictures taken as an admin, and a preview seeds a preview-only admin and a small fixture so those pages have something to show. Production refuses both. main has been merged in: the forge.json conflict is resolved with main's page list (/repos replaced /migrate) and this branch's "looksAsOwner": ["/", "/flow", "/talk"].

What changed

  • src/apps/manifest.ts: new field app.looksAsOwner. Each entry is a page path checked like those in app.looks, and each must also appear in app.looks, or the manifest is refused. Leaving it out means no pages. The path checks shared by looks and looksAsOwner are now one pathList helper; the error messages for app.looks read as before.
  • src/apps/runtime.ts: the environment status now carries looksAsOwner.
  • src/looks/service.ts: for a listed page, phone and desktop are taken with the owner's admin grant and recorded as takenAs: "owner", so only writers can fetch them (mayFetchLook). Pages not listed, and every thumb, are still taken as nobody on a public app.
  • src/spawn/preview-seed.ts (new): when FORGE_APP_ENV=preview, it creates preview-admin once. The account has no password and no session. It also seeds a public repo preview-admin/sample with two issues and an open pull, three agent runs that spent this week, a valve turned to 60% (the flow week), and two board panels pinned to home. Anywhere else it answers not_preview and creates nothing.
  • src/spawn/preview-owner.ts (new), wired into resolveAuth in src/http/server.ts and set up in src/main.ts:

- In a preview only, a request whose x-forge-identity names an admin, is signed with FORGE_APP_IDENTITY_KEY and has not expired, is treated as the preview admin. - It counts as an edge identity, so changes are still held to the same-origin check. - Production never reads the key, so the headers are ignored there. - Anyone the parent forge marks as admin of the repo is a writer, and can already push code that runs over a preview's data, so this gives them nothing new.

  • forge.json: "looksAsOwner": ["/", "/flow", "/talk"].
  • Docs: docs/features/preview-owner-looks.md (new) and a paragraph under "The look" in docs/APPS.md.

What to look at The asNobody(kind, page) rule in src/looks/service.ts and the ordering in resolveAuth. Also the choice to reuse via: "edge": it gives the same-origin check without adding a new kind of sign-in. The catch is that the sign-out link in a preview points at the platform's logout.

Not done here The desk and phone looks of home and /flow that #8698 and #8726 asked for will not appear on this pull. Looks are taken by the parent forge, which runs main and ignores the new field until this merges. The first pull after merge will carry them.

How it was checked

  • After the merge, bun scripts/gate.ts and bun x tsc --noEmit pass. test/looks-owner.test.ts (new), forge-looks, refresh-meta, edge-auth and here-manifest also pass: 82 tests, none failing.
  • The new test covers:

- the manifest field; - the seed being refused in production and with no environment set, and running once in a preview; - a signed admin seeing /flow, while a reader, a forged signature or an expired one gets nothing; - the same-origin check on changes; - production ignoring the headers; - a page listed as owner getting the admin grant while the others stay nobody.

  • Before the merge, the wider set also covered test/looks.test.ts, signin-edge, apps-adversarial and board-pins. In that run, three real-Chromium capture tests in test/looks.test.ts failed; they fail the same way on unchanged main in this sandbox.
  • Before the merge I also booted it with FORGE_APP_ENV=preview and a signed admin identity. The seed ran, / answered 200, /flow answered 200 in 18 ms as the preview admin, home showed both pins, and an anonymous /flow was redirected to sign in.

Try it Run FORGE_APP_ENV=preview FORGE_APP_IDENTITY_KEY=k FORGE_DATA=$(mktemp -d) bun run src/main.ts. Then request /flow with x-forge-identity: u1|you|admin|<expiry ms> and x-forge-signature set to the base64url HMAC-SHA256 of that identity string under k.

🤖 Generated with Claude Code

commented

A preview of the forge can now be pictured signed in as its owner. Pages that forge.json lists under the new app.looksAsOwner get their phone and desktop pictures taken as an admin, and a preview seeds a preview-only admin and a small fixture so those pages have something to show. Production refuses both.

What changed

  • src/apps/manifest.ts: new field app.looksAsOwner. Each entry is a page path checked like those in app.looks, and each must also appear in app.looks, or the manifest is refused. Leaving it out means no pages. The path checks shared by looks and looksAsOwner are now one pathList helper; the error messages for app.looks read as before.
  • src/apps/runtime.ts: the environment status now carries looksAsOwner.
  • src/looks/service.ts: for a listed page, phone and desktop are taken with the owner's admin grant and recorded as takenAs: "owner", so only writers can fetch them (mayFetchLook). Pages not listed, and every thumb, are still taken as nobody on a public app.
  • src/spawn/preview-seed.ts (new): when FORGE_APP_ENV=preview, it creates preview-admin once. The account has no password and no session. It also seeds a public repo preview-admin/sample with two issues and an open pull, three agent runs that spent this week, a valve turned to 60% (the flow week), and two board panels pinned to home. Anywhere else it answers not_preview and creates nothing.
  • src/spawn/preview-owner.ts (new), wired into resolveAuth in src/http/server.ts and set up in src/main.ts:

- In a preview only, a request whose x-forge-identity names an admin, is signed with FORGE_APP_IDENTITY_KEY and has not expired, is treated as the preview admin. - It counts as an edge identity, so changes are still held to the same-origin check. - Production never reads the key, so the headers are ignored there. - Anyone the parent forge marks as admin of the repo is a writer, and can already push code that runs over a preview's data, so this gives them nothing new.

  • forge.json: "looksAsOwner": ["/", "/flow", "/talk"], next to main's current page list.
  • test/signup-gate-parent.test.ts: adds src/spawn/preview-seed.ts: ["seed"] to its list of every place an account is made. The list is updated by hand whenever a new caller is added, as its comment says. The test was right; this is the deliberate update it asks for, and it fixes the one failure in the last check.
  • Docs: docs/features/preview-owner-looks.md (new) and a paragraph under "The look" in docs/APPS.md.

What to look at The asNobody(kind, page) rule in src/looks/service.ts and the ordering in resolveAuth. Also the choice to reuse via: "edge": it gives the same-origin check without adding a new kind of sign-in. The catch is that the sign-out link in a preview points at the platform's logout.

Not done here The desk and phone looks of home and /flow that #8698 and #8726 asked for will not appear on this pull. Looks are taken by the parent forge, which runs main and ignores the new field until this merges. The first pull after merge will carry them.

How it was checked

  • bun scripts/gate.ts and bun x tsc --noEmit pass.
  • test/signup-gate-parent.test.ts and test/looks-owner.test.ts pass: 12 tests, none failing.
  • The new test/looks-owner.test.ts covers:

- the manifest field; - the seed being refused in production and with no environment set, and running once in a preview; - a signed admin seeing /flow, while a reader, a forged signature or an expired one gets nothing; - the same-origin check on changes; - production ignoring the headers; - a page listed as owner getting the admin grant while the others stay nobody.

  • After merging main, forge-looks, refresh-meta, edge-auth and here-manifest also passed.
  • Before the merge I also booted it with FORGE_APP_ENV=preview and a signed admin identity. The seed ran, / answered 200, /flow answered 200 in 18 ms as the preview admin, home showed both pins, and an anonymous /flow was redirected to sign in.

Try it Run FORGE_APP_ENV=preview FORGE_APP_IDENTITY_KEY=k FORGE_DATA=$(mktemp -d) bun run src/main.ts. Then request /flow with x-forge-identity: u1|you|admin|<expiry ms> and x-forge-signature set to the base64url HMAC-SHA256 of that identity string under k.

🤖 Generated with Claude Code

Approved

Clean on security: identity is HMAC-verified (constant-time, expiry checked), only read when FORGE_APP_ENV=preview, rides the edge path so mutations keep the same-origin rule, and the seeded admin has no password or session. Tests cover each case and the docs are in docs/features. One non-blocking note: seedWorld is not atomic, so a failure after createRepo leaves a half-made sample repo and every later boot fails on the name. Consider checking for an existing repo or cleaning up on error. The home and /flow looks (box 3) cannot appear until this lands, as the pull says.

commented

Left #8812 open: 1 unticked item remain (The first pull carries desk and phone looks of home with pins and of /flow. Those are the boxes #8698 and #8726 left.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs