Public: anyone can read this; the crew can change it.
Bring the remaining docs in line with docs/WRITING.md #9525
Check · Review · Land · Live — Changes were asked on this head.
mason/forge:mason/9426-bring-the-remaining-docs-in-line-with-do → main 1 commit fork
1 file
changed +127
−87
docs/STATUS.md +127 −87
| @@ -3,32 +3,84 @@ | ||
| 3 | 3 | This document lists the features that work in today's forge. It is for |
| 4 | 4 | operators and contributors who need a concrete inventory of shipped |
| 5 | 5 | capability. Each bullet names one feature and the doors that reach it. |
| 6 | Claims match the code on the default branch; cut or fix anything you | |
| 7 | cannot confirm. A package registry is not shipped yet. | |
| 6 | Claims match the code on the default branch. A package registry is not | |
| 7 | shipped yet. | |
| 8 | 8 | |
| 9 | ## Features | |
| 9 | ## Accounts and access | |
| 10 | ||
| 11 | - **Accounts & access**: register/sign in, personal access tokens with coarse | |
| 12 | scopes (ro/rw/admin), public and private repositories. | |
| 13 | ||
| 14 | - **Where an account came from**: `GET /api/v1/admin/accounts` lists each | |
| 15 | account with `via` (`signup`, `edge`, or null) and `signsInThrough` | |
| 16 | (`"open-platform.sh"` for an account the platform edge made, else null); | |
| 17 | Settings → Accounts and the inbox's new-account line say "signs in | |
| 18 | through open-platform.sh" for the same accounts. The terminal and the MCP | |
| 19 | have no accounts list to carry it. | |
| 20 | ||
| 21 | - **Removing an account**: `DELETE /api/v1/admin/accounts/:username` (admin | |
| 22 | only). An account that is the last owner of an organization answers 409 with | |
| 23 | the organizations named. Send `{"retireOrgs": true}` to preview: the answer | |
| 24 | lists each organization, its repositories (and the app each runs) and its | |
| 25 | teams, and changes nothing; the account's own repositories and the app each | |
| 26 | runs are listed too, under `own`. Send `{"retireOrgs": true, "confirm": true}` to | |
| 27 | do it: apps are stopped, repositories deleted, organizations retired (the | |
| 28 | name stays taken), then the account. If a repository will not go, the answer | |
| 29 | is 500 with `failed` and `removed`; what is left is intact and the same call | |
| 30 | again finishes the job. The accounts page offers the same flow: Remove → | |
| 31 | "Remove the organization too" → a confirm page listing everything that goes. | |
| 32 | ||
| 33 | - **Organizations & teams**: orgs own repos; teams grant read/write/admin | |
| 34 | resolved through membership. | |
| 35 | ||
| 36 | ## Code hosting | |
| 37 | ||
| 38 | - **Git**: push and pull over HTTPS and SSH, browse code (tree, blob with line anchors, | |
| 39 | commit history) with README rendering and syntax highlighting, protected | |
| 40 | default branches (force-push and deletion refused, honest fast-forwards | |
| 41 | allowed). | |
| 42 | ||
| 43 | - **Issues**: labels, threaded comments, close/reopen, markdown. | |
| 44 | ||
| 45 | - **Pull requests**: open from a branch, a **diff viewer** (syntax | |
| 46 | highlighting composed with intra-line word highlighting, mobile-first, | |
| 47 | collapsible files), merge-commit and squash strategies, conflict detection, | |
| 48 | and merge commits kept reachable forever via `refs/pull/N/merged`. | |
| 49 | ||
| 50 | - **Review comments**: anchored to a diff line by content hash, so a | |
| 51 | force-push re-anchors them or marks them outdated, never misplaces them. | |
| 52 | ||
| 53 | - **Forks**: alternates-backed (instant, storage-bounded), with cross-repo | |
| 54 | pull requests (fork → upstream) and detach-before-delete safety. | |
| 55 | ||
| 56 | - **Commit pages**: every commit's diff through the same viewer. | |
| 10 | 57 | |
| 11 | 58 | - **A long file's page** draws its first 1,500 lines and says "N more |
| 12 | 59 | lines", with *Show all lines* (`?all=1`) and the raw file. `?all=1` stops at |
| 13 | 10,000 lines (it was 5,000 for every view before); past that, the raw file is | |
| 60 | 10,000 lines; past that, the raw file is | |
| 14 | 61 | the way. Highlighted lines are cached by content hash (32 files, least |
| 15 | 62 | recently used out). A `#L…` anchor past the cut is redirected to `?all=1` by |
| 16 | 63 | the page script; with scripts off the notice says to show all lines. |
| 17 | - **A repository's runs page filters by state**: a row of links (All, then each | |
| 18 | state) above the list, or `/:owner/:name/runs?state=failed` by hand. An unknown | |
| 19 | state is ignored, and the word filter and the pager keep the chosen state. The | |
| 20 | terminal does the same: `H` in a repository opens its runs, and `s` cycles | |
| 21 | All, Failed, Running, Succeeded. | |
| 22 | - **A run page's live log** rides the page's one `/events` connection (its | |
| 23 | `run.log` frames); it opens no stream of its own. Known gaps, both silent: a | |
| 24 | frame the server cuts for size (64 lines or 16 KB) loses its omitted lines | |
| 25 | until the next reconnect's catch-up read, and a run whose log was already cut | |
| 26 | to its last 512 KB at render gets no catch-up read. The page says | |
| 27 | "Reconnecting" while the connection is down. | |
| 64 | ||
| 65 | - **Settings**: visibility, branch protection, and labels. | |
| 66 | ||
| 67 | - **Live updates**: a server-sent-event stream turns comment threads live and | |
| 68 | flags new activity, no refresh. | |
| 69 | ||
| 70 | - **Origins**: connect GitHub, Forgejo or Gitea (device code, PKCE, or a | |
| 71 | pasted token), mirror repositories in with their issues and pulls, and write | |
| 72 | through to the origin on every comment, state change, label and push. Pins | |
| 73 | say what the local client keeps cloned. | |
| 74 | ||
| 75 | - **Automation**: a JSON API covering repos, issues, forks, and orgs (the | |
| 76 | label-as-queue surface), a durable event outbox, and **webhooks** (HMAC-signed | |
| 77 | delivery with retry). Every send goes through `src/kernel/outbound.ts`: | |
| 78 | - https URLs only; the host is resolved, private addresses are refused, and no redirect is followed. | |
| 79 | - A refused delivery fails at once, with the reason shown on the hooks page. | |
| 80 | - An unreachable or erroring endpoint is retried with growing delay. | |
| 28 | 81 | |
| 29 | 82 | - **Cut a wave**: Settings → *Cut a wave* (repository admins, same-origin |
| 30 | POST) opens a pull | |
| 31 | adding `waves/<next>.md`, listing the pulls merged since the last wave and | |
| 83 | POST) opens a pull adding `waves/<next>.md`, listing the pulls merged since the last wave and | |
| 32 | 84 | not already in an earlier wave. The headline and highlights are `TODO` |
| 33 | 85 | placeholders; no model is called. It refuses, writing nothing, when an |
| 34 | 86 | earlier wave does not parse (naming the file) or nothing new has merged. |
| @@ -46,15 +98,38 @@ cannot confirm. A package registry is not shipped yet. | ||
| 46 | 98 | the waves page; a private repository's feed is a 404 to anyone who cannot |
| 47 | 99 | read it. |
| 48 | 100 | |
| 49 | - **Accounts & access**: register/sign in, personal access tokens with coarse | |
| 50 | scopes (ro/rw/admin), public and private repositories. | |
| 51 | - **Git**: push and pull over HTTPS **and SSH**, browse code (tree, blob with line anchors, | |
| 52 | commit history) with README rendering and **syntax highlighting**, protected | |
| 53 | default branches (force-push and deletion refused, honest fast-forwards | |
| 54 | allowed). | |
| 55 | - **System memory**: `/system`, for a forge admin who is a person (anyone else gets 403): the forge's RSS beside its JS heap (`heapStats()` from bun:jsc), hourly mean and max for the last 24 h, drawn on the server. Sampled once a minute into `memory_hour` in `app.db` (a week kept). RSS rising while the heap stays flat is native memory growth. In the terminal the same admin has a **System memory** command in the palette: the last 24 hours as text rows (hour, RSS mean/max, heap mean/max in MiB), read-only. The account menu links `/system` for that admin. | |
| 56 | - **Flow alarms**: a firing alarm (red main, a stuck approved pull, an idle crew, no release for 90 min) shows as one linked line above home's feed and on `/boards` (admin), under the vitals line of the terminal's Flow dialog and home, as one phone notification when it starts (not repeated while it stands), and in the `flow` tool's `alarms` (MCP, `/api/v1/telemetry/flow`; an agent's cut to public repositories). Nothing firing draws nothing. The dialog also lists the idle crew pulls (number, repository, hours since the last push), oldest first, at most 20 read and five drawn with `+N more`; none draws nothing, and an agent's list is cut to public repositories. | |
| 57 | - **Clog watch**: the minute tick (`src/flow/clog_tick.ts`) reads the flow; when a clog starts the admin gets one phone notification, and when it clears one `clog.cleared` note (cause, time to clear, peak stranded $) lands once on the admin's feed and as a line on the terminal's home. The watch state is in the database, so a restart repeats neither. | |
| 101 | ## Apps and crew | |
| 102 | ||
| 103 | - **Apps**: a repository that runs: production on the default branch, a | |
| 104 | preview per open pull with its own copy of the data, served by hostname | |
| 105 | from the forge's own port, every deploy a run and every run a check. | |
| 106 | ||
| 107 | - **The crew**: agents as accounts with nothing but a run-scoped token, | |
| 108 | working issues in the background with Claude Code and proposing through | |
| 109 | pulls a person merges. | |
| 110 | ||
| 111 | - **The `flow` label**: ready issues labelled `flow` go first; no switch, no state. | |
| 112 | ||
| 113 | - **The check lane is visible.** Checks run one at a time; an admin's home | |
| 114 | strip, the terminal dashboard's strip and `GET /api/v1/health` (`lane`) show | |
| 115 | the queue depth, the running check and its age, the median of the last | |
| 116 | twenty and the time to clear, and the 30-minute queue card names that cause. | |
| 117 | ||
| 118 | - **A repository's runs page filters by state**: a row of links (All, then each | |
| 119 | state) above the list, or `/:owner/:name/runs?state=failed` by hand. An unknown | |
| 120 | state is ignored, and the word filter and the pager keep the chosen state. The | |
| 121 | terminal does the same: `H` in a repository opens its runs, and `s` cycles | |
| 122 | All, Failed, Running, Succeeded. | |
| 123 | ||
| 124 | - **A run page's live log** rides the page's one `/events` connection (its | |
| 125 | `run.log` frames); it opens no stream of its own. Known gaps, both silent: a | |
| 126 | frame the server cuts for size (64 lines or 16 KB) loses its omitted lines | |
| 127 | until the next reconnect's catch-up read, and a run whose log was already cut | |
| 128 | to its last 512 KB at render gets no catch-up read. The page says | |
| 129 | "Reconnecting" while the connection is down. | |
| 130 | ||
| 131 | ## Boards and flow | |
| 132 | ||
| 58 | 133 | - **Boards**: `/boards` also shows the pipeline (a row per stage, the bottleneck named) and the held counts (why each autonomous repository's ready issues wait; private repositories included), for the same admin who is a person; they sit as two cards under the board list, not as panels of a board, because they are not saved queries. The terminal's Boards screen does not draw them: its door to the same rows is the **Flow** dialog. `/boards/data`, for a forge admin who is a person (anyone else |
| 59 | 134 | gets a 404 on the page): the query is the URL (`?source=runs&aggregate=count&by=day&fcolumn=state&fvalue=failed`), |
| 60 | 135 | drawn server-side as the chart chosen beside it (`chart=line|bar|area|number|table|heat`) |
| @@ -77,72 +152,37 @@ cannot confirm. A package registry is not shipped yet. | ||
| 77 | 152 | The first boot also writes the starting boards, **Usage**, **Serving**, **Crew**, **Releases**, **Apps**, **Security**, **Throughput**, **Reach** and **Forges**; Serving is the forge's event-loop lag p99 beside the checks and crew runs running, so checks' concurrency is raised on numbers. Apps' per-app panels show repo ids until a source exposes a safe name; Security (sign-ins, failed sign-ins, tokens by scope, admin actions, from the admin-only `signins`, `tokens` and `adminActions` sources and `usage`) shows no token text, hash, payload or IP; Throughput is items entering each stage from issue to live, per hour, from the `stages` source. Each is written once: `.offered` in that repo records them, so an edit stays and a deletion sticks. |
| 78 | 153 | A live board (any `refresh` but `off`; the form offers off or on, and `10s` and `1m` are still accepted and mean the same) redraws its grid when a run, pull, check, issue or usage event arrives, through the live-region swap |
| 79 | 154 | (`/boards/:slug/live`, etag'd, one fetch per burst). The same doors are in the API: `GET /api/v1/boards`, `GET|PUT|DELETE /api/v1/boards/:slug`, and each panel's data as JSON or CSV at `GET /api/v1/boards/:slug/panels/:id?from=&to=&step=5m&format=csv`, bucketed in SQL to the step and never cut short (`docs/APPS.md`); each panel's card links `json` and `csv` (a plain download link) beside `explore` |
| 80 | (an agent or anyone but an admin is 403; a panel whose query cannot run is refused with 422). The same four doors are MCP tools (`boards`, `board_write`, `board_delete`; `boards` with a `slug` reads one, and with a `panel` too that panel's data, with the same `from`, `to` and `step`) and, for the same person, a **Boards** screen in the terminal that lists boards and shows a board's panels as text, each with what its data holds now (a time panel's last bucket and step, another's row count). No dependency was added. | |
| 155 | (an agent or anyone but an admin is 403; a panel whose query cannot run is refused with 422). The same four doors are MCP tools (`boards`, `board_write`, `board_delete`; `boards` with a `slug` reads one, and with a `panel` too that panel's data, with the same `from`, `to` and `step`) and, for the same person, a **Boards** screen in the terminal that lists boards and shows a board's panels as text, each with what its data holds now (a time panel's last bucket and step, another's row count). | |
| 156 | ||
| 157 | - **Flow alarms**: a firing alarm (red main, a stuck approved pull, an idle crew, no release for 90 min) shows as one linked line above home's feed and on `/boards` (admin), under the vitals line of the terminal's Flow dialog and home, as one phone notification when it starts (not repeated while it stands), and in the `flow` tool's `alarms` (MCP, `/api/v1/telemetry/flow`; an agent's cut to public repositories). Nothing firing draws nothing. The dialog also lists the idle crew pulls (number, repository, hours since the last push), oldest first, at most 20 read and five drawn with `+N more`; none draws nothing, and an agent's list is cut to public repositories. | |
| 158 | ||
| 159 | - **Clog watch**: the minute tick (`src/flow/clog_tick.ts`) reads the flow; when a clog starts the admin gets one phone notification, and when it clears one `clog.cleared` note (cause, time to clear, peak stranded $) lands once on the admin's feed and as a line on the terminal's home. The watch state is in the database, so a restart repeats neither. | |
| 160 | ||
| 81 | 161 | - **Parked work**: the terminal's Flow dialog says how many issues the crew parked (an open issue whose newest comment is the crew's `Parked by the crew` note) in the last 24 h and 7 d, and names the newest three; the MCP `flow` tool returns the same as `parked`. The flow panel on `/boards` draws two lines under its numbers, each left out at 0: `N idle pulls` (open crew pulls whose head has not moved for 72 h; the oldest three linked, `and K more` after) and `N parked today, M this week` (linking the newest parked issue). The Flow dialog lists them too (`idle: N`, a row each, `+K more`). `/boards` is the owner's; the MCP `flow` tool, as an agent, reads public repositories only (sealed issues left out). `and K more` opens `/boards?idle=all`, a list of every idle pull. See `docs/TOUR.md`. |
| 82 | - **Issues**: labels, threaded comments, close/reopen, markdown. | |
| 83 | - **Pull requests**: open from a branch, a **diff viewer** (syntax | |
| 84 | highlighting composed with intra-line word highlighting, mobile-first, | |
| 85 | collapsible files), merge-commit and squash strategies, conflict detection, | |
| 86 | and merge commits kept reachable forever via `refs/pull/N/merged`. | |
| 87 | - **Review comments**: anchored to a diff line by content hash, so a | |
| 88 | force-push re-anchors them or marks them outdated, never misplaces them. | |
| 89 | - **Forks**: alternates-backed (instant, storage-bounded), with cross-repo | |
| 90 | pull requests (fork → upstream) and detach-before-delete safety. | |
| 91 | - **Organizations & teams**: orgs own repos; teams grant read/write/admin | |
| 92 | resolved through membership. | |
| 93 | - **Live updates**: a server-sent-event stream turns comment threads live and | |
| 94 | flags new activity, no refresh. | |
| 95 | - **Commit pages**: every commit's diff through the same viewer. | |
| 96 | - **Settings**: visibility, branch protection, and labels. | |
| 97 | - **The check lane is visible.** Checks run one at a time; an admin's home | |
| 98 | strip, the terminal dashboard's strip and `GET /api/v1/health` (`lane`) show | |
| 99 | the queue depth, the running check and its age, the median of the last | |
| 100 | twenty and the time to clear, and the 30-minute queue card names that cause. | |
| 162 | ||
| 163 | - **System memory**: `/system`, for a forge admin who is a person (anyone else gets 403): the forge's RSS beside its JS heap (`heapStats()` from bun:jsc), hourly mean and max for the last 24 h, drawn on the server. Sampled once a minute into `memory_hour` in `app.db` (a week kept). Rising RSS with a flat heap means native memory growth. In the terminal the same admin has a **System memory** command in the palette: the last 24 hours as text rows (hour, RSS mean/max, heap mean/max in MiB), read-only. The account menu links `/system` for that admin. | |
| 164 | ||
| 165 | ## Terminal | |
| 166 | ||
| 101 | 167 | - **A terminal UI**: `ssh` in for the whole forge: dashboard, code browsing |
| 102 | 168 | with syntax highlighting, issues and pulls with live-updating threads, the |
| 103 | 169 | diff viewer with inline review comments, merges, settings, orgs, tokens and |
| 104 | 170 | keys. Six themes, remembered per account. |
| 105 | - **Origins**: connect GitHub, Forgejo or Gitea (device code, PKCE, or a | |
| 106 | pasted token), mirror repositories in with their issues and pulls, and write | |
| 107 | through to the origin on every comment, state change, label and push. Pins | |
| 108 | say what the local client keeps cloned. | |
| 109 | - **Automation**: a JSON API covering repos, issues, forks, and orgs (the | |
| 110 | label-as-queue surface), a durable event outbox, and **webhooks** (HMAC-signed | |
| 111 | delivery with retry). Every send goes through `src/kernel/outbound.ts`: | |
| 112 | - https URLs only; the host is resolved, private addresses are refused, and no redirect is followed. | |
| 113 | - A refused delivery fails at once, with the reason shown on the hooks page. | |
| 114 | - An unreachable or erroring endpoint is retried with growing delay. | |
| 115 | - **Where an account came from**: `GET /api/v1/admin/accounts` lists each | |
| 116 | account with `via` (`signup`, `edge`, or null) and `signsInThrough` | |
| 117 | (`"open-platform.sh"` for an account the platform edge made, else null); | |
| 118 | Settings → Accounts and the new-account line in the inbox say "signs in | |
| 119 | through open-platform.sh" for the same accounts. The terminal and the MCP | |
| 120 | have no accounts list to carry it. | |
| 121 | - **Removing an account**: `DELETE /api/v1/admin/accounts/:username` (admin | |
| 122 | only). An account that is the last owner of an organization answers 409 with | |
| 123 | the organizations named. Send `{"retireOrgs": true}` to preview: the answer | |
| 124 | lists each organization, its repositories (and the app each runs) and its | |
| 125 | teams, and changes nothing; the account's own repositories and the app each | |
| 126 | runs are listed too, under `own`. Send `{"retireOrgs": true, "confirm": true}` to | |
| 127 | do it: apps are stopped, repositories deleted, organizations retired (the | |
| 128 | name stays taken), then the account. If a repository will not go, the answer | |
| 129 | is 500 with `failed` and `removed`; what is left is intact and the same call | |
| 130 | again finishes the job. The accounts page offers the same flow: Remove → | |
| 131 | "Remove the organization too" → a confirm page listing everything that goes. | |
| 132 | - **Apps**: a repository that runs: production on the default branch, a | |
| 133 | preview per open pull with its own copy of the data, served by hostname | |
| 134 | from the forge's own port, every deploy a run and every run a check. | |
| 135 | - **The crew**: agents as accounts with nothing but a run-scoped token, | |
| 136 | working issues in the background with Claude Code and proposing through | |
| 137 | pulls a person merges. | |
| 138 | - **The `flow` label**: ready issues labelled `flow` go first; no switch, no state. | |
| 139 | 171 | |
| 140 | Pages render without a line of JavaScript and are designed thumb-first. Six | |
| 141 | premade themes (from warm paper to polar night) switch instantly and persist in | |
| 142 | a cookie; even that works script-free. Still **zero runtime dependencies**. | |
| 172 | ## Rendering and themes | |
| 143 | 173 | |
| 144 | Built since: every app, preview and check runs in a bubblewrap cell | |
| 145 | (`src/apps/contain.ts`), an app can take a custom domain (MCP `app_domains`, | |
| 146 | `src/http/mcp/tools/app_domains.ts`), and a pull whose review and checks agree | |
| 174 | Pages render without JavaScript and are designed for a phone first. Six | |
| 175 | themes (from warm paper to polar night) switch instantly and persist in a | |
| 176 | cookie; that works without scripts too. The forge has no runtime dependencies. | |
| 177 | ||
| 178 | ## Isolation and landing | |
| 179 | ||
| 180 | Every app, preview and check runs in a bubblewrap cell | |
| 181 | (`src/apps/contain.ts`). An app can take a custom domain (MCP `app_domains`, | |
| 182 | `src/http/mcp/tools/app_domains.ts`). A pull whose review and checks agree | |
| 147 | 183 | lands on its own (`src/checks/landing.ts`, the merge queue in |
| 148 | `src/checks/queue.ts`). Not yet: a package registry. | |
| 184 | `src/checks/queue.ts`). | |
| 185 | ||
| 186 | ## Not done | |
| 187 | ||
| 188 | There is no package registry. |