@bongos/core 1.19.426 → 1.19.427

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.426",
6
- "core_contract": "1.19.426",
7
- "source_commit": "6e32dc947ac0c7879f4df33ac9a6642485b921f9",
5
+ "core_version": "1.19.427",
6
+ "core_contract": "1.19.427",
7
+ "source_commit": "f0b176008c51428ad1c344b7704d0e9462c0ee90",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-29T19:24:28.557Z",
9
+ "built_at": "2026-08-29T19:37:42.056Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 361,
12
+ "docs_redacted": 362,
13
13
  "agent_docs_stubbed": 23,
14
- "functional_verbatim": 1729,
14
+ "functional_verbatim": 1731,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2113,
20
- "tree_sha256": "dbe9e6dd3b2d878e12342577089f1fb18739cf3b570acea9df12a15b9ba6a42b",
19
+ "file_count": 2116,
20
+ "tree_sha256": "96e06bbbfd609aa582c5ac2461392edc56e3c51afdbdb2d2551165711fd75276",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -1554,10 +1554,15 @@
1554
1554
  "mode": "0000644",
1555
1555
  "sha256": "e7cc54c2186bda5ae76039a57ab285320bdd57c6879a51e55bcd145fe9261ad5"
1556
1556
  },
1557
+ {
1558
+ "path": "docs/adr/0212-connections-on-the-profile-surface-and-addressing.md",
1559
+ "mode": "0000644",
1560
+ "sha256": "1251b6ba7b5fc79765aed5fcf9696d6f55d52acf6d6766200215a480f2d574a3"
1561
+ },
1557
1562
  {
1558
1563
  "path": "docs/adr/README.md",
1559
1564
  "mode": "0000644",
1560
- "sha256": "3aa9b5f26de5b7e4911ce1ee8debb6fd58dc1543e487a1ec1a51e5995a6a473c"
1565
+ "sha256": "c4216e812144d5ed7b95c3ffd25c124b8e7a1f97610721e3b7ab938751f881c7"
1561
1566
  },
1562
1567
  {
1563
1568
  "path": "docs/api-reference.md",
@@ -1592,12 +1597,12 @@
1592
1597
  {
1593
1598
  "path": "docs/copy-inventory.md",
1594
1599
  "mode": "0000644",
1595
- "sha256": "f554b543f8f13d00649476abd2aba52d973eae5db9dc1fede6956c63f1d6e0e7"
1600
+ "sha256": "054f4d38105b2bfad142c1a72b982f09e78bd3b14074eac1b2a001c49b8cd2bf"
1596
1601
  },
1597
1602
  {
1598
1603
  "path": "docs/copy-registry.json",
1599
1604
  "mode": "0000644",
1600
- "sha256": "adb2bd7b686eb3943c7bdf8c2129d0de3e80ebb9db99b7507a4320a569beb383"
1605
+ "sha256": "e6fbf0872ac28c4931fbec5bc335b50283d4e0b1d7247e300612eb0c6ec653d0"
1601
1606
  },
1602
1607
  {
1603
1608
  "path": "docs/design/apex-pass-2-direction.md",
@@ -1942,7 +1947,7 @@
1942
1947
  {
1943
1948
  "path": "docs/file-map.md",
1944
1949
  "mode": "0000644",
1945
- "sha256": "a5b1b7926513202fefed48db17bb50fa7727a15ee48e6981b524fc4f76db28ca"
1950
+ "sha256": "4690b1019567fe7e15f8e7a3c873b9cef94534bd07124545a388f9cd8bf85918"
1946
1951
  },
1947
1952
  {
1948
1953
  "path": "docs/handoff-template.md",
@@ -1952,7 +1957,7 @@
1952
1957
  {
1953
1958
  "path": "docs/module-api-changelog.md",
1954
1959
  "mode": "0000644",
1955
- "sha256": "b32e4941a78d82f7cbbdb5a42608ebb46370b46088ce56432e4c8ec19c2365ec"
1960
+ "sha256": "3af56594001e0448960dd5999bf603e94c008f31d7c24997e6af2d4e0f52f550"
1956
1961
  },
1957
1962
  {
1958
1963
  "path": "docs/modules-contract.md",
@@ -4114,6 +4119,11 @@
4114
4119
  "mode": "0000644",
4115
4120
  "sha256": "40f6f44283aa7dba25a2c983d77b6e6f53f40eefef3394122d34f7f42fc5f676"
4116
4121
  },
4122
+ {
4123
+ "path": "modules/hall-ui/public/profile-connections.js",
4124
+ "mode": "0000644",
4125
+ "sha256": "b6ff1827da188dbd5a36091e6f5634d43ac21d386cc04b553b6f46c92827dff2"
4126
+ },
4117
4127
  {
4118
4128
  "path": "modules/hall-ui/public/profile-nudge.js",
4119
4129
  "mode": "0000644",
@@ -4122,12 +4132,12 @@
4122
4132
  {
4123
4133
  "path": "modules/hall-ui/public/profile.html",
4124
4134
  "mode": "0000644",
4125
- "sha256": "437a1d29edb129f2c21aaa842a57e06058e4355c14c8b0deba6576c83672a90d"
4135
+ "sha256": "3b48f9fedd44d702a4323bd7cbdc0e3f570ca50d2940c6f22508d6c5bc482f8e"
4126
4136
  },
4127
4137
  {
4128
4138
  "path": "modules/hall-ui/public/profile.js",
4129
4139
  "mode": "0000644",
4130
- "sha256": "d1e73a781b88743c5cfe21452d738989e631d30f12e38a2589a6519fc7b4b81e"
4140
+ "sha256": "0885bb6b92dfdc54b9177b0cb949a692ebc8025a019f309d6d786f9dfaf2442d"
4131
4141
  },
4132
4142
  {
4133
4143
  "path": "modules/hall-ui/public/ranks.html",
@@ -4217,7 +4227,7 @@
4217
4227
  {
4218
4228
  "path": "modules/hall-ui/public/style.css",
4219
4229
  "mode": "0000644",
4220
- "sha256": "316a9cc264946e81f554dbd882604ad11bcf63de8799fb74803dbcb4958edd3f"
4230
+ "sha256": "41dcbf8e7b21b95c134cfa7df182fc1059d67a4185c38fc55ff310ecde4df5aa"
4221
4231
  },
4222
4232
  {
4223
4233
  "path": "modules/hall-ui/public/task-detail.js",
@@ -5532,12 +5542,12 @@
5532
5542
  {
5533
5543
  "path": "package-lock.json",
5534
5544
  "mode": "0000644",
5535
- "sha256": "c16499122c1680ba466c0245ddb69d6225fd9152e76f9e626c8522ff237cd20b"
5545
+ "sha256": "0c38238be602951a19181a4e65e8e2544a06c25ecb4968dc952ff84e4a70bb2f"
5536
5546
  },
5537
5547
  {
5538
5548
  "path": "package.json",
5539
5549
  "mode": "0000644",
5540
- "sha256": "ba7f2f7a6795a2ecd9316195ff8ac0df597e077f7d8bce4efed6d45a5945ea88"
5550
+ "sha256": "e796318cb273523ec008c90d6105ad81b10a7acb9460a15063610e552c70856b"
5541
5551
  },
5542
5552
  {
5543
5553
  "path": "public-docs/index.html",
@@ -7302,7 +7312,7 @@
7302
7312
  {
7303
7313
  "path": "scripts/hall-preview/server.js",
7304
7314
  "mode": "0000644",
7305
- "sha256": "a55d67cab28ca941ad47a016bb5ed66d2fe1f410d0557544ee79ab57adc70006"
7315
+ "sha256": "0a0d26971c469980b398fe9d7ba1cd85013a704ca0e188d8055ee26c8dfa8044"
7306
7316
  },
7307
7317
  {
7308
7318
  "path": "scripts/migrate.sh",
@@ -7667,7 +7677,7 @@
7667
7677
  {
7668
7678
  "path": "src/module-api.js",
7669
7679
  "mode": "0000644",
7670
- "sha256": "4ca579815846fc51d427439c0e5e9d15e6102d628fe97b013c0234d59fe691c2"
7680
+ "sha256": "25a7279a6e6870a9e0ce02a7a5a4e78891c2d37c5a0cb7cd623ad1369a756885"
7671
7681
  },
7672
7682
  {
7673
7683
  "path": "src/module-loader/catalog.js",
@@ -9689,6 +9699,11 @@
9689
9699
  "mode": "0000644",
9690
9700
  "sha256": "013df2528c4998588c1d3f89a9a25f8fa1e912b8abc2d8e95c26c250869a386f"
9691
9701
  },
9702
+ {
9703
+ "path": "tests/profile_connections_ui.mjs",
9704
+ "mode": "0000644",
9705
+ "sha256": "1165d03c123ace3558f2a17e06730e37bd6556e98d91f40c2b17fea11cd54353"
9706
+ },
9692
9707
  {
9693
9708
  "path": "tests/profile_fields.mjs",
9694
9709
  "mode": "0000644",
@@ -0,0 +1,61 @@
1
+ # ADR 0212 — The connections section is built on the profile page that exists, and it addresses a builder by asking the API to name them
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-08-29
5
+ - **Task:** [task 1002296](https://cloudbongos.com/builders#/task/1002296) (R20 · goal 1000045 — Public builder profiles, criterion C6)
6
+ - **Amends:** [ADR 0202](<redacted>.md) D4 (the design of record) and [ADR 0207](<redacted>.md) (the API's own departures). Neither decision changes; this records where the *rendering* had to choose, and what the chosen surface cannot yet do.
7
+ - **Relates to:** ADR 0016 (a connection is never authority), ADR 0171 D4/D5 (leak by subtraction; no-store), `docs/specs/<redacted>.md` (the page spec whose section 6 this is)
8
+
9
+ > ADR **0210** and **0211** were spoken for by sibling sessions in flight; this ADR takes 0212 rather than race them (the ADR 0199 / 0202 / 0207 precedent).
10
+
11
+ ## Context
12
+
13
+ R20 says "connections on the profile". The profile-page spec's section table marks section 6 **Hub ✓ / Instance —**, meaning the canonical `cloudbongos.com/u/<handle>` page. That page **does not exist**: it is goal 1000048 R14 / [task 1002365](https://cloudbongos.com/builders#/task/1002365), and [task 1002806](https://cloudbongos.com/builders#/task/1002806) established with evidence that there is no route, no page, no hub search entry and no sitemap row for it. Meanwhile `modules/hall-ui/public/profile.html` — the live profile at `/builders/profile` — carries a comment R04 left there on purpose: *"Reserved slots — connections, guilds, and actions (profile-page spec sections 6–8) mount here in later phases."*
14
+
15
+ Two problems follow, and they are the only two this ADR decides.
16
+
17
+ 1. **Which page.** Build nothing and wait for a page nobody has scheduled, or build into the slot that was reserved for exactly this.
18
+ 2. **How to name the other builder.** Every connections route addresses a counterparty by **handle** (ADR 0202 D1, ADR 0171 D8). The profile page's read, core `GET /builders/:id/profile`, whitelists nine public columns and **`handle` is not among them** — it knows only `github_login`. Widening that whitelist is [task 1002804](https://cloudbongos.com/builders#/task/1002804)'s canonical-URL work, not this one's.
19
+
20
+ ## Decision
21
+
22
+ ### D1 — Section 6 lands on the hall profile page, revealed only where the hub answers
23
+
24
+ The section mounts into R04's reserved slot on `modules/hall-ui/public/profile.{html,js}`, as a separate script (`profile-connections.js`) so neither the page script nor `platform-identity.js` grows against its line ratchet.
25
+
26
+ It follows the **cross-project record's** contract exactly, because that is the precedent for a hub-only section on this page: the markup ships `hidden`, and the script reveals it only when the hub's routes answered with something to show. On an instance without `platform-identity` every call 404s and the section never appears — degrade, don't 500 (profile-page spec constraint 4).
27
+
28
+ This does **not** pre-empt the canonical page. When `/u/<handle>` is built it renders the same three reads; two surfaces consuming one own-scope API is the composition ADR 0202 D4 §4 already describes (a viewer-relative number cannot ride a viewer-independent public response, so the *page* composes it from a second authenticated call — which is true of whichever page does the composing).
29
+
30
+ **Rejected.** *Waiting for the hub page* — R20's dependents ([task 1002298](https://cloudbongos.com/builders#/task/1002298), the C6 proof) would block on a task in a different goal with no owner. *Building `/u/<handle>` here* — the spec says in terms that only one such page gets built, and it is not this task's.
31
+
32
+ ### D2 — The page hands the API the login it holds; the API hands back the handle, and every write uses that
33
+
34
+ `profile-connections.js` calls `GET /connections/degree/:handle` with the **github_login** it already renders on screen, and then uses the **`handle` the response echoes** for the connect write — never the login it started from.
35
+
36
+ This works because `findAccountByHandle` resolves the current handle *or* the single-slot `previous_handle`, and the route answers with `target.handle`. The API's own header says why it is built that way: *"an ACTION endpoint follows a rename rather than answering 301, so a stale 'Connect' link on a page still works."* Asking it to resolve a login is the same move a stale link makes.
37
+
38
+ The failure mode is the right one. A login that resolves to neither slot answers **404 — the same 404 as an unknown handle, a provisional account, and a private stranger** — and on all of them the section stays hidden and no connect button is drawn. So the degradation is exactly the set of cases where the button must not render anyway, plus one it arguably should: **a builder who renamed their handle twice loses the connect affordance on this surface.** That is stated here rather than discovered later. The fix is not a login→handle oracle; it is the handle-addressed page (D1), which never makes this hop.
39
+
40
+ **Rejected.** *Adding `handle` to `GET /builders/:id/profile`* — a core kernel file, its leak test, and the `rel=canonical` wiring that task 1002804 owns; two sessions editing that whitelist in the same week is the collision this coordination exists to avoid. *Putting `handle` inside `cross_project`* — that payload is `null` for a hidden account, and ADR 0202 D4 §3 is explicit that `hide_stats` suppresses the numbers beside an edge and never the edge, so the handle would vanish precisely where the section must still work. *Resolving through the public `GET /profiles/:handle`* — it 404s for a private account, including one the viewer is connected to, which D4 §2 says is addressable to them.
41
+
42
+ ### D3 — Three rendering rules, each the last place a privacy decision can be undone
43
+
44
+ The API can only keep its promises if the screen keeps them too. Three are enforced by `tests/profile_connections_ui.mjs` rather than by memory.
45
+
46
+ 1. **`degree: null` renders "Out of network", never "not connected"** — ADR 0202's Consequences, made literal. And the copy beside it is **constant**: it must read identically whether the response said `truncated` or not, because the target's own 1-hop truncation is deliberately unreported (ADR 0207 L2) and copy that varied with the flag would rebuild that side channel in words. A degree the API never defined falls to the same label rather than being invented one.
47
+ 2. **A sent request claims exactly `201 { status: 'pending' }`** — "Request sent", disabled, and nothing about notification or acceptance. A re-ask swallowed by the 90-day cooldown answers identically and writes nothing, so on the next load the button is simply back to "Connect" **with no explanation anywhere** (ADR 0207 L1). Adding one would be the decline leaking through the UI after the API refused to leak it.
48
+ 3. **Every count is the length of the list beside it**, and `seen_at` and `truncated` never reach the screen.
49
+
50
+ ## Consequences
51
+
52
+ - Criterion C6's UI half exists on a real page today, and the canonical page inherits it as three own-scope calls rather than a rewrite.
53
+ - One honest gap, named above: a twice-renamed handle has no connect button on the instance profile. It fails closed, and it disappears when `/u/<handle>` lands.
54
+ - The section is descriptive only. Nothing it renders is read by `requireRank`, `requirePermission` or any admission gate, and nothing it renders is rollup-derived — which is why `hide_stats` does not suppress it (ADR 0016, ADR 0202 D4 §3).
55
+ - The proof runs the real router over a WHERE-derived fake pool on a real socket with the real client above it, so a change that makes the *API* less careful — a private stranger becoming addressable, the cooldown ceasing to swallow, the caller's consent floor dropping — fails this UI test too, not only the API's own.
56
+
57
+ ## Deferred (named, not silently dropped)
58
+
59
+ - **A public per-builder connections list.** There is deliberately no such route: `GET /connections` is own-scope, so another builder's profile shows the viewer-relative label and the mutuals the viewer is entitled to, and nothing else. Publishing someone's connection list is a privacy-matrix question for the owner, not a builder (ADR 0202's "connections-specific visibility toggle" deferral).
60
+ - **Removing a connection from the other person's profile.** Removal lives on your own list, where the row and its id already are; putting it on their page would need a third read for one button.
61
+ - **Section 7 (guilds) and the wider section 8 actions row.** C7 and the governance-gated invite are their own tasks; the slot comment still names them.
@@ -297,6 +297,7 @@ This keeps the decision history honest and traceable.
297
297
  | 0208 | [**Building the application lifecycle: the handle waits for its writer, and the vouch ADR 0201 D2.1 describes has no minter that can express it** ([task 1002284](https://cloudbongos.com/builders#/task/1002284), goal 1000045 — R08, criteria C3/C4). Builds [ADR 0201](<redacted>.md) D1 + D4 + D3.2 and records where the design could not be written literally. **The contradiction:** D1.2 says the migration adds "exactly" `applicant_github_id` + `vouched_at`; D2.6 says the applicant's handle is stored on the row. Decision — the handle waits for R09 ([task 1002285](https://cloudbongos.com/builders#/task/1002285)), because the ADR's own dependency table routes D2 *wholesale* (including the handle) there, and because R08 ships no vouch writer at all: a permanently-NULL `applicant_handle` would read as "this account has no handle" when it means "R09 has not shipped". **The hole:** D2.1's replay separation cannot be built as described. `mintHubAssertion` takes no `purpose`, and the asymmetry protecting `mintHubLogoutToken` works only because a logout token carries no identity — an application vouch carries both, so it collides with the login assertion's shape; worse, `verifyHubAssertion` does not reject a payload carrying a `purpose`, so a vouch minted that way would verify *as a login assertion* today. R09 needs a third mint/verify pair plus a tightening of `verifyHubAssertion` (free only while no minter emits a `purpose`), and a deliberate choice about the single-use `jti` store. Also decided: the echo's READ joins `platform_identity_sso_clients` (a `client_id` alone is not renderable) while the TABLE stays the three columns D1.3 named; a re-application refreshes `applied_at`, so the 30-day window runs from the most recent ask; an idempotent PATCH returns the row **without** re-stamping `resolved_by`/`resolved_at`, so the audit trail records who actually resolved it; `dismissed → invited` is refused `409 already_dismissed` naming `POST /access-requests/invite`; and `GET /my-projects` drops from `applied` any project the caller now builds on, so the hub never implies an outcome it is not allowed to hold. Honest limits: `vouched` is false for every real row until R09 lands (the two-class queue is proved by a seeded row, not by a live writer); `already_approved` still reaches the hub as "already_member" prose through `joinRelayOutcome`'s bare-409 branch, which is R10/R12's surface; and the proofs are DB-free, so the partial unique index is modelled rather than exercised — five mutations were run to show the fake pool bites. Rejected: adding `applicant_handle` anyway; bolting a `purpose` argument onto `mintHubAssertion`; a `status` column on the echo; refusing `dismissed → invited` with the existing `not_pending`; a new "project owner" atom.](<redacted>.md) | platform identity / admission lifecycle |
298
298
  | 0209 | [**Two routes answering one question spend one budget; whether the answer itself collapses is the owner's call** ([task 1003339](https://cloudbongos.com/builders#/task/1003339), goal 1000045 — criterion `i-control-my-own-access-and-exposure`). `GET /access-requests/status` (public, returns `member|invited|pending|dismissed|none`) and `GET /auth/web/admission-status` (its bare-boolean twin) both answer "does this GitHub login have an account here?", and neither was throttled — `rateLimitMiddleware` returns early for every non-write method, and the one read limiter is mounted only under `/public/*`. Scripted at line rate either becomes a login→membership roster: who builds here, and confirmation that a named person is present. Decision: ship the ceiling as ONE budget — `accountExistenceReadRateLimit`, 120 reads/60s per IP over a single bucket, exported as one middleware INSTANCE rather than a factory and mounted on both routes, because two limiters each configured 120/60s look identical in a diff, pass any test that counts to the limit per route, and hand an enumerator 240/min by alternating. The onboarding module reaches it through the doorway, so `src/bongos/middleware/rate-limit.js` joins `KERNEL_FILES` on `logger.js`'s argument: authz decides WHO may ask, the ceiling decides HOW OFTEN, and the file imports nothing. Deliberately NOT decided here: collapsing the response so a non-subject caller cannot tell `pending`/`dismissed`/`none` from "not admitted" — the consumer sweep found route 1 has exactly ONE consumer (`scripts/gds/login.js`), which already treats `pending` and `none` identically, so collapsing costs exactly one thing: the only message in the codebase that tells a declined applicant they were declined. That is a judgement about what an applicant is owed, so it goes to the owner as a blocker with the evidence. Noted either way: collapsing does not close the oracle — `admitted:true` still means member-or-invited. Rejected: a separate limiter per route; a `createReadRateLimit` factory (two matching call sites get two buckets — the generality loses the property); vendoring a fourth copy of the sliding window; hardening only the smaller route; collapsing on my own judgement because "nothing much" reads it.](<redacted>.md) | security / rate limiting |
299
299
  | 0207 | [**Building the connection graph: three departures from ADR 0202, and the three limits it is honest about** ([task 1002295](https://cloudbongos.com/builders#/task/1002295), goal 1000045 criterion C6 — the build of the connections wave). ADR 0202 decided the graph; building it surfaced three places where the decision's MECHANISM could not be written literally without losing the PROPERTY it was about, and three places where the shipped code cannot promise as much as a casual reading suggests. Departures: (1) **level-wise CTEs, not `WITH RECURSIVE`** — a recursive term can carry neither a per-level fan-out cap nor an ordered per-level truncation, which are the two things D3 actually decided, so a recursive build would have had a depth bound and no fan-out bound; a test asserts `RECURSIVE` appears in neither query. (2) **1st degree is an unbounded pair-index probe**, never a scan of the capped frontier — reading it off the `FANOUT_1`-truncated 1-hop reports "2nd" for someone in the viewer's own connections list once they pass 500 connections, and the rule this states is that **a cap may only ever make an answer less specific (`null`), never wrong**. (3) The shared visibility predicate lands in its own file, `modules/platform-identity/account-visibility.js` (`isAccountVisible` / `accountVisibleSql` / `accountActiveSql`), because task 1002967 had not shipped and `platform-identity.js` sat at the size ratchet with zero headroom — **1002967 adopts it rather than writing a second predicate**. Also decided: node visibility during traversal is viewer-relative and asymmetric on purpose (your OWN 1-hop floors on consent alone so a friend who goes private does not vanish from your list; every deeper hop clears the public predicate); cancel is a `DELETE` an ADDRESSEE may not use, so the 90-day cooldown cannot be dodged by calling a decline a delete; and the create response is `201 { status: 'pending' }` and nothing else, so a swallowed re-ask has nothing to fabricate. Limits stated rather than implied: a swallowed re-ask is silent in the RESPONSE and observable by its ABSENCE from the requester's outbox (the claim is narrowed, not the code — a decoy row would be a lie in the database to protect a lie in the API); the TARGET's 1-hop is capped too and that truncation is deliberately NOT reported, because saying so leaks one bit about a third party's degree; and the query COUNT is bounded while the frontier's WORK is O(sum of degree over the 1-hop), since `ORDER BY … LIMIT` materialises before it truncates — the revisit trigger is a measured p95, and the cheap next move is a `statement_timeout` returning the `null` D3 already chose. Deferred and named: a per-builder cap on OUTBOUND requests (the cooldown stops repeat asks to one person, not one builder asking a hundred — a real number is owner-signed), and a real-Postgres pass on the expire-then-insert write path, which CI cannot run.](<redacted>.md) | platform identity / social graph / privacy |
300
+ | 0212 | [**The connections section is built on the profile page that exists, and it addresses a builder by asking the API to name them** ([task 1002296](https://cloudbongos.com/builders#/task/1002296), R20 · goal 1000045 criterion C6 — the UI half of the connections wave). The profile-page spec puts section 6 on the canonical `cloudbongos.com/u/<handle>`, which [task 1002806](https://cloudbongos.com/builders#/task/1002806) proved does not exist (no route, no page, no search entry, no sitemap row) and which belongs to goal 1000048 R14. Decisions: (1) **section 6 lands in the reserved slot R04 left on the hall profile page** (`modules/hall-ui/public/profile.{html,js}`), as a separate `profile-connections.js` so neither the page script nor `platform-identity.js` grows against its line ratchet, and it follows the CROSS-PROJECT section’s contract exactly — the markup ships `hidden` and reveals only where the hub answered, so an instance without `platform-identity` never renders it. It does not pre-empt the canonical page, which inherits the same three own-scope calls. (2) **Addressing: the page hands the API the `github_login` it holds and uses the `handle` the response echoes back for every write** — core `GET /builders/:id/profile` whitelists nine columns and `handle` is not one of them, and widening that whitelist is task 1002804’s canonical-URL work. `findAccountByHandle` already resolves current-or-previous handle and the route answers with the canonical one, which is the same move the API built for a stale Connect link. It fails closed into the deliberately indistinguishable 404 (unknown / provisional / private stranger), so the ONE honest gap is stated rather than discovered: **a twice-renamed handle has no connect button on this surface**, and it disappears when `/u/<handle>` lands. (3) Three rendering rules, enforced by `tests/profile_connections_ui.mjs` (real router over a WHERE-derived fake pool on a real socket, real client above it): `degree: null` renders "Out of network" and never "not connected", with copy that is BYTE-IDENTICAL whether `truncated` was set — varying it would rebuild the ADR 0207 L2 side channel in words; a sent request claims exactly `201 { status: 'pending' }`, so a re-ask swallowed by the 90-day cooldown simply shows "Connect" again next load with no explanation anywhere; and every count is the length of the list beside it, with `seen_at` and `truncated` never reaching the screen. Rejected: waiting for the hub page (it would block C6’s proof on another goal); building `/u/<handle>` here (the spec says only one such page gets built); adding `handle` to the core profile whitelist or to `cross_project` (the latter is `null` under `hide_stats`, which is exactly where the edge must still show — ADR 0202 D4 §3); resolving through public `GET /profiles/:handle` (it 404s for a private account the viewer IS connected to). Deferred and named: a public per-builder connections list (there is deliberately no such route), removal from the other person’s profile, and sections 7–8.](<redacted>.md) | platform identity / social graph / hall UI |
300
301
  | 0210 | [**The recruiting directory graduates on an instance-asserted ownership claim, and the recruiting opt-out gates the directory and nothing else** ([task 1002290](https://cloudbongos.com/builders#/task/1002290), R14, goal 1000045 criterion C5 — "project owners, not only Archons, browse builder profiles to recruit"). Two independent problems wore one task id. The reach flag `recruiter_discoverable` protected nobody — task 1002967 said so, and the Settings row stayed in the future tense because of it. And "widen the Archon-only /scouting" was already stale (ADR 0157 lowered `project.curate` to metic) while hiding the real question: **a project owner is not a rank**, and someone who registers a project is an Archon on their OWN instance and usually a bare `xenos` on the hub where the directory lives, so no floor reaches them without reaching every stranger. **D1** "project owner" on the hub is `<redacted>.membership_kind = 'owner'` — written by the PROJECT itself at `POST /sso/membership/check-in` from its own first-admin config, and refused unless the hub's own `signin_verified_at` witness stands (ADR 0205) — and never `platform_identity_projects.owner_github_id`, the hub-curated column ADR 0201 D3 names and forbids; the prohibition is about a hub-held list consulted before an ADMISSION on someone else's box, and this is the hub reading the instance's own mirrored claim before a read of the hub's OWN directory. **D2** the two doors are an OR in `modules/platform-identity/scouting-authz.js` (the `goal-authz.js` owner-or-rank shape, kept out of `platform-identity.js` at its size ratchet): ownership is asked first and a "no" falls through to the UNMODIFIED `requirePermission`, so a refusal keeps its exact code, `required`/`held` and floors map — the widening can only admit; the atom is spelled in the ROUTE's middleware list, because a gate hidden in the helper would make `route-rank-check.js` and `gen-api-docs.js` record this privileged route as `any-builder`; and a broken ownership read falls closed onto the rank door rather than admitting or 500ing. No atom is minted — `project.recruit` belongs to the console wave (spike 1002786 R3). **D3** `PAGE_GATES` gains an optional `alsoAdmits` naming a function on the `platform-identity.hub` port, declared on `requireProjectCuratePage` alone and consulted only after the permission check refused, so the `/people` shell admits exactly the two audiences its data route does; absent port, absent function or a throw all deny. **D4** the reach COALESCE gets its SQL twin in `account-visibility.js` (its sole owner) and composes into `scoutingListableSql` = rollup AND reach, which `listScoutingRollup` joins — but reach is deliberately NOT folded into `rollupVisibleSql`, because the community spec D3 says the opt-out governs the recruiting surface only and folding it down would silently empty a future public leaderboard. **D5** no migration: both columns exist and migration 003's `(github_id)` index already serves the probe. **D6** the ownership query JOINs `platform_identity_sso_clients` on `status='active'` — a membership row never expires and nothing ages it out, so suspending the client (the lever that already stops a project signing anyone in) had to revoke the read it derives, or suspending a rogue project would leave its owner reading every builder on the platform forever. The task's own adversarial review also found the sole-owner drift guard was matching only the UNALIASED spelling of the reach COALESCE — i.e. not the one that ships — and two fail-open defaults (a prototype-reachable `alsoAdmits`, and an alias guard resting on template evaluation order); all three fixed and mutation-checked. ⚠ Honest limits recorded, not fixed: the hall NAV still hides People from a project owner (task 1003349, sequenced with R15 which owns that surface), the `touchMembership` write path stays as ADR 0205 left it, and `openapi.json` cannot express "or owner". ⚠ And DELEGATES do not reach the directory yet (blocker 1000122 + task 1003350, linked) — an instance reports `owner` for its first admin only, so the registrant half works and the delegate half needs the instance to report `owner` for any archon, which changes a cross-version wire contract and a shipped bucketing; filed as blocker 1000122, and nothing here changes when it is answered. Rejected: lowering the floor further (a floor low enough to admit a registrant excludes nobody); a `project.owner` atom (ADR 0201 D3 rejected it); minting `project.recruit` here; gating on `owner_github_id`; folding reach into the rollup predicate; widening `membershipKind()` on our own judgement; shipping the route without the page.](<redacted>.md) | platform identity / authz + privacy |
301
302
 
302
303
  > ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
@@ -4,13 +4,13 @@
4
4
  > Deterministic on a given tree — regenerate and commit rather than hand-editing.
5
5
  > Machine-readable twin: [`copy-registry.json`](copy-registry.json).
6
6
 
7
- **1572 strings** across 3 surfaces · 1367 distinct pieces of copy · 1572 certain, 0 heuristic · 156 shown in more than one place.
7
+ **1580 strings** across 3 surfaces · 1374 distinct pieces of copy · 1580 certain, 0 heuristic · 157 shown in more than one place.
8
8
 
9
9
  ## Surfaces
10
10
 
11
11
  | surface | lives at | files | strings |
12
12
  |---|---|---|---|
13
- | `builders-hall` | `modules/hall-ui/public` | 72 | 1156 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 73 | 1164 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 98 |
15
15
  | `landing` | `public-landing` | 5 | 318 |
16
16
 
@@ -76,9 +76,9 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
76
76
  | `563d29ed86d8` | 2x | Bulk actions | modules/hall-ui/public/goals.html:148, modules/hall-ui/public/work.html:118 |
77
77
  | `46b33550d52b` | 2x | Bulk claim | modules/hall-ui/public/goals.html:151, modules/hall-ui/public/work.html:121 |
78
78
  | `c10806b50c61` | 2x | Claim task {…} | modules/hall-ui/public/goals-render.js:280, modules/hall-ui/public/work.js:417 |
79
- | … | | _116 more in the JSON registry_ | |
79
+ | … | | _117 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1156 strings
81
+ ## `builders-hall` — 1164 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -297,7 +297,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
297
297
  | `8f597eabf4ac` | certain | Could not load the drachmae guide. | `modules/hall-ui/public/drachmae.js:162` |
298
298
  | `b2147cc90e36` | certain | Could not load the idea template. Reload, or file from the terminal with capture.js. | `modules/hall-ui/public/ideas.js:438` |
299
299
  | `6264ebc9ede0` | certain | Could not load the primer. | `modules/hall-ui/public/primer.js:620` |
300
- | `7604af13c40e` | certain | Could not load the profile. | `modules/hall-ui/public/profile.js:615` |
300
+ | `7604af13c40e` | certain | Could not load the profile. | `modules/hall-ui/public/profile.js:620` |
301
301
  | `8b69e445592a` | certain | Could not load the question labels; showing the answers as stored. | `modules/hall-ui/public/idea.js:229` |
302
302
  | `edd5bafbe075` | certain | Could not load the queue just now. | `modules/hall-ui/public/ideas.js:387` |
303
303
  | `02bb0570996a` | certain | Could not load the ranks. | `modules/hall-ui/public/ranks.js:197` |
@@ -739,6 +739,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
739
739
  | `ab456f09c0f6` | certain | Request access | `modules/hall-ui/public/hall-render.js:405` |
740
740
  | `967f6d1ac068` | certain | Request dev box | `modules/hall-ui/public/box-panel.js:357` |
741
741
  | `967f6d1ac068` | certain | Request dev box | `modules/hall-ui/public/box-panel.js:421` |
742
+ | `820f47ce5f16` | certain | Request sent | `modules/hall-ui/public/profile-connections.js:82` |
743
+ | `820f47ce5f16` | certain | Request sent | `modules/hall-ui/public/profile-connections.js:130` |
742
744
  | `e86e43684bce` | certain | Request sent — the owner or a manager will review it. | `modules/hall-ui/public/goals-render.js:999` |
743
745
  | `88b36bd9368b` | certain | Request to join | `modules/hall-ui/public/my-projects.html:47` |
744
746
  | `280f20e7ddf5` | certain | Request withdrawn. | `modules/hall-ui/public/goals-render.js:1015` |
@@ -802,6 +804,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
802
804
  | `838b17fa8d93` | certain | Shown on your public profile — it helps others know what you work on. | `modules/hall-ui/public/profile-nudge.js:69` |
803
805
  | `2f12d46f96ca` | certain | Sign in from the app. | `modules/hall-ui/public/hall-render.js:650` |
804
806
  | `19ab56cc3932` | certain | Sign in to filter to your goals | `modules/hall-ui/public/goals-page.js:866` |
807
+ | `969f95f183ca` | certain | Sign in to the platform once to activate your profile before connecting. | `modules/hall-ui/public/profile-connections.js:216` |
805
808
  | `b057dfbf49c5` | certain | Sign in when Claude prompts you. | `modules/hall-ui/public/hall-render.js:650` |
806
809
  | `e57ec24e13ef` | certain | Sign in with GitHub | `modules/hall-ui/public/hall-render.js:256` |
807
810
  | `e57ec24e13ef` | certain | Sign in with GitHub | `modules/hall-ui/public/hall-render.js:327` |
@@ -815,6 +818,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
815
818
  | `82df09a08476` | certain | So the Archon knows who you are | `modules/hall-ui/public/hall-render.js:275` |
816
819
  | `7da6a848cd26` | certain | Someone helped | `modules/hall-ui/public/collab.js:65` |
817
820
  | `867df58c9aaa` | certain | Something else | `modules/hall-ui/public/copy-desk.html:97` |
821
+ | `c377d8ed744b` | certain | Something went wrong. Try again. | `modules/hall-ui/public/profile-connections.js:271` |
818
822
  | `7e4b5d31df51` | certain | Sort by karma (peer recognition + approvals) | `modules/hall-ui/public/hall-widgets.js:210` |
819
823
  | `f8ca223d8864` | certain | Sort by rank | `modules/hall-ui/public/hall-widgets.js:208` |
820
824
  | `d67798e25c67` | certain | Sort by tokens used across recorded sessions — efficiency is tokens ÷ credits (hover a row for tokens-per-drac | `modules/hall-ui/public/hall-widgets.js:211` |
@@ -848,6 +852,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
848
852
  | `545c59c1deb7` | certain | That is the session you are using right now. Ending it signs you out of this page. | `modules/hall-ui/public/settings-sessions.js:74` |
849
853
  | `4a8d1beb6f98` | certain | That project isn’t on the platform. | `modules/hall-ui/public/my-projects.js:145` |
850
854
  | `c0e28f30524d` | certain | That project's own standing, as reported by that project. | `modules/hall-ui/public/profile.js:230` |
855
+ | `e99d4a811e92` | certain | That request is no longer available. | `modules/hall-ui/public/profile-connections.js:266` |
851
856
  | `50198c465225` | certain | The 16-value rank enum in three bands, which ranks are live, and the permissions each rank holds. | `modules/hall-ui/public/diagrams.html:68` |
852
857
  | `1569b8838862` | certain | The Dev Box app now acts as | `modules/hall-ui/public/pair.js:40` |
853
858
  | `47c47af5dd7f` | certain | The Repo Atlas regenerates itself on the server every time code ships. It looks like it hasn’t been generated | `modules/hall-ui/public/atlas.html:48` |
@@ -887,6 +892,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
887
892
  | `bcdbded93d4e` | certain | The work board failed to load. Reload to try again. | `modules/hall-ui/public/work.js:1402` |
888
893
  | `25794c439321` | certain | Then copy the project onto your machine and run the one-shot bootstrap (it checks your setup, installs depende | `modules/hall-ui/public/hall-render.js:717` |
889
894
  | `df316c3484de` | certain | Then drive it however you like: | `modules/hall-ui/public/hall-render.js:650` |
895
+ | `5151cf635644` | certain | They have asked to connect. | `modules/hall-ui/public/profile-connections.js:199` |
890
896
  | `6e46811189db` | certain | This PR is held by the gate-review check. | `modules/hall-ui/public/gate.js:113` |
891
897
  | `ed313d96b419` | certain | This builder keeps their track record private. | `modules/hall-ui/public/profile.js:210` |
892
898
  | `3f9e476e22db` | certain | This clears {…}. The gate check re-runs and the PR merges once it goes green. Your approval applies only to co | `modules/hall-ui/public/gate.js:87` |
@@ -1160,6 +1166,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1160
1166
  | `c5623d60015d` | certain | {…} Your standing is {…}. | `modules/hall-ui/public/hall-render.js:729` |
1161
1167
  | `418b4031e9a4` | certain | {…} \u00b7 who votes, and how it {…} | `modules/hall-ui/public/government.js:963` |
1162
1168
  | `a91e1e702d4d` | certain | {…} achieved, {…} in progress, {…} not started of {…} goals | `modules/hall-ui/public/roadmap.js:80` |
1169
+ | `0da0e247ff0f` | certain | {…} asking to connect | `modules/hall-ui/public/profile-connections.js:132` |
1163
1170
  | `9d160147b34a` | certain | {…} criteria · since {…} | `modules/hall-ui/public/roadmap.js:140` |
1164
1171
  | `9d160147b34a` | certain | {…} criteria · since {…} | `modules/hall-ui/public/roadmap.js:163` |
1165
1172
  | `78ba2bd4bc44` | certain | {…} criteria · started {…} | `modules/hall-ui/public/roadmap.js:223` |
@@ -1186,6 +1193,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1186
1193
  | `8e28c14b106d` | certain | {…} total | `modules/hall-ui/public/roadmap.js:108` |
1187
1194
  | `44e2f95586e4` | certain | {…} will be able to open dev boxes again. | `modules/hall-ui/public/harbor.js:176` |
1188
1195
  | `b2db97589086` | certain | {…} will not be able to open or wake a dev box until you unblock them. A box they already have running is left | `modules/hall-ui/public/harbor.js:146` |
1196
+ | `3ad8f32dd43b` | certain | {…} you have sent | `modules/hall-ui/public/profile-connections.js:133` |
1189
1197
  | `8186b562553a` | certain | {…} {…} waiting | `modules/hall-ui/public/work.js:356` |
1190
1198
  | `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/work.js:220` |
1191
1199
  | `b3757ba3bb46` | certain | {…} · Stored encrypted; never shown again after saving. | `modules/hall-ui/public/settings.js:1036` |
@@ -3,17 +3,17 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1572,
7
- "unique_ids": 1367,
8
- "certain": 1572,
6
+ "strings": 1580,
7
+ "unique_ids": 1374,
8
+ "certain": 1580,
9
9
  "heuristic": 0,
10
- "duplicate_ids": 156
10
+ "duplicate_ids": 157
11
11
  },
12
12
  "surfaces": {
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
- "files": 72,
16
- "strings": 1156
15
+ "files": 73,
16
+ "strings": 1164
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -1177,6 +1177,16 @@
1177
1177
  "modules/hall-ui/public/box-panel.js:421"
1178
1178
  ]
1179
1179
  },
1180
+ {
1181
+ "id": "820f47ce5f16",
1182
+ "surface": "builders-hall",
1183
+ "text": "Request sent",
1184
+ "occurrences": 2,
1185
+ "at": [
1186
+ "modules/hall-ui/public/profile-connections.js:82",
1187
+ "modules/hall-ui/public/profile-connections.js:130"
1188
+ ]
1189
+ },
1180
1190
  {
1181
1191
  "id": "0887725744f2",
1182
1192
  "surface": "builders-hall",
@@ -3589,7 +3599,7 @@
3589
3599
  "surface": "builders-hall",
3590
3600
  "text": "Could not load the profile.",
3591
3601
  "file": "modules/hall-ui/public/profile.js",
3592
- "line": 615,
3602
+ "line": 620,
3593
3603
  "origin": "js-notice",
3594
3604
  "confidence": "certain"
3595
3605
  },
@@ -7562,6 +7572,24 @@
7562
7572
  "origin": "js-markup",
7563
7573
  "confidence": "certain"
7564
7574
  },
7575
+ {
7576
+ "id": "820f47ce5f16",
7577
+ "surface": "builders-hall",
7578
+ "text": "Request sent",
7579
+ "file": "modules/hall-ui/public/profile-connections.js",
7580
+ "line": 82,
7581
+ "origin": "js-markup",
7582
+ "confidence": "certain"
7583
+ },
7584
+ {
7585
+ "id": "820f47ce5f16",
7586
+ "surface": "builders-hall",
7587
+ "text": "Request sent",
7588
+ "file": "modules/hall-ui/public/profile-connections.js",
7589
+ "line": 130,
7590
+ "origin": "js-markup",
7591
+ "confidence": "certain"
7592
+ },
7565
7593
  {
7566
7594
  "id": "e86e43684bce",
7567
7595
  "surface": "builders-hall",
@@ -8129,6 +8157,15 @@
8129
8157
  "origin": "js-text-assign",
8130
8158
  "confidence": "certain"
8131
8159
  },
8160
+ {
8161
+ "id": "969f95f183ca",
8162
+ "surface": "builders-hall",
8163
+ "text": "Sign in to the platform once to activate your profile before connecting.",
8164
+ "file": "modules/hall-ui/public/profile-connections.js",
8165
+ "line": 216,
8166
+ "origin": "js-markup",
8167
+ "confidence": "certain"
8168
+ },
8132
8169
  {
8133
8170
  "id": "b057dfbf49c5",
8134
8171
  "surface": "builders-hall",
@@ -8246,6 +8283,15 @@
8246
8283
  "origin": "html-text",
8247
8284
  "confidence": "certain"
8248
8285
  },
8286
+ {
8287
+ "id": "c377d8ed744b",
8288
+ "surface": "builders-hall",
8289
+ "text": "Something went wrong. Try again.",
8290
+ "file": "modules/hall-ui/public/profile-connections.js",
8291
+ "line": 271,
8292
+ "origin": "js-notice",
8293
+ "confidence": "certain"
8294
+ },
8249
8295
  {
8250
8296
  "id": "7e4b5d31df51",
8251
8297
  "surface": "builders-hall",
@@ -8543,6 +8589,15 @@
8543
8589
  "origin": "js-markup",
8544
8590
  "confidence": "certain"
8545
8591
  },
8592
+ {
8593
+ "id": "e99d4a811e92",
8594
+ "surface": "builders-hall",
8595
+ "text": "That request is no longer available.",
8596
+ "file": "modules/hall-ui/public/profile-connections.js",
8597
+ "line": 266,
8598
+ "origin": "js-notice",
8599
+ "confidence": "certain"
8600
+ },
8546
8601
  {
8547
8602
  "id": "50198c465225",
8548
8603
  "surface": "builders-hall",
@@ -8894,6 +8949,15 @@
8894
8949
  "origin": "js-markup",
8895
8950
  "confidence": "certain"
8896
8951
  },
8952
+ {
8953
+ "id": "5151cf635644",
8954
+ "surface": "builders-hall",
8955
+ "text": "They have asked to connect.",
8956
+ "file": "modules/hall-ui/public/profile-connections.js",
8957
+ "line": 199,
8958
+ "origin": "js-markup",
8959
+ "confidence": "certain"
8960
+ },
8897
8961
  {
8898
8962
  "id": "6e46811189db",
8899
8963
  "surface": "builders-hall",
@@ -11351,6 +11415,15 @@
11351
11415
  "origin": "js-markup",
11352
11416
  "confidence": "certain"
11353
11417
  },
11418
+ {
11419
+ "id": "0da0e247ff0f",
11420
+ "surface": "builders-hall",
11421
+ "text": "{…} asking to connect",
11422
+ "file": "modules/hall-ui/public/profile-connections.js",
11423
+ "line": 132,
11424
+ "origin": "js-markup",
11425
+ "confidence": "certain"
11426
+ },
11354
11427
  {
11355
11428
  "id": "9d160147b34a",
11356
11429
  "surface": "builders-hall",
@@ -11585,6 +11658,15 @@
11585
11658
  "origin": "js-markup",
11586
11659
  "confidence": "certain"
11587
11660
  },
11661
+ {
11662
+ "id": "3ad8f32dd43b",
11663
+ "surface": "builders-hall",
11664
+ "text": "{…} you have sent",
11665
+ "file": "modules/hall-ui/public/profile-connections.js",
11666
+ "line": 133,
11667
+ "origin": "js-markup",
11668
+ "confidence": "certain"
11669
+ },
11588
11670
  {
11589
11671
  "id": "8186b562553a",
11590
11672
  "surface": "builders-hall",
package/docs/file-map.md CHANGED
@@ -244,7 +244,7 @@
244
244
  │ ├── terrain.js ← (Phase D + ADR 0012, CJS): TERRAIN + OBJECT enums + BLOCKING_TERRAINS + BLOCKING_OBJECTS (server twin of public/game/world/terrain.js)
245
245
  │ └── map.js ← (Phase D + ADR 0012): two-grid INITIAL_MAP {ground, objects}; isBlocked checks both layers (mirrors public/game/world/{terrain,map}.js)
246
246
  │ (public-status/ moved to modules/status-ui/public/ in BV1.R84 — see the modules/ block above)
247
- │ (public-builders/ moved to modules/hall-ui/public/ in BV1.R85 — the authenticated builders' hall; see the modules/ block above. It keeps its DEDICATED serving block in src/bongos/serve-internal.js: the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection — partitioned out of the generic web-surface loop. index.html=the hall dashboard; work/board/roadmap/goals/watch/harbor/gate/pair/settings/ranks/sessions/primer/diagrams/atlas pages; ideas.{html,js,css}=the Full Idea composer (task 1002928 / R152 of goal 1000062 — the five R141 fields with a live R142 completeness meter, both fetched from the server so the page carries no copy of the rules; drafts in localStorage so navigation costs nothing. Task 1002930 / R154 added the SPARK QUEUE above it and a second mode: `?develop=<id>` reuses the same form to develop a spark — the spark's words shown above the fields, its title read-only, and the file routed to POST /inbox/:id/develop instead of POST /inbox, under its own per-idea draft key); idea.html/idea.js=the idea-detail PAGE (/idea/:id, task 1002929 / R153 — the SECOND two-segment hall page after /task/:id: grade, the five answers in template order, the score, and both attributions. The GRADE decides what the page contains — a quick idea gets no five-field skeleton. task-detail.js keeps the #/idea/N overlay because ~320 of its lines are the Metic+ triage panel; the overlay now links here); profile.html/profile.js=the public, shareable builder profile page (/profile?id=N — rank, works, credits, karma, achievements; task 1467); task.html/task.js=the task-detail page (/task/:id, task 751 — replaced the old #/task/N overlay); builders.js=the controller; dom-utils.js/hall-widgets.js=the widget+settings-panel registries.)
247
+ │ (public-builders/ moved to modules/hall-ui/public/ in BV1.R85 — the authenticated builders' hall; see the modules/ block above. It keeps its DEDICATED serving block in src/bongos/serve-internal.js: the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection — partitioned out of the generic web-surface loop. index.html=the hall dashboard; work/board/roadmap/goals/watch/harbor/gate/pair/settings/ranks/sessions/primer/diagrams/atlas pages; ideas.{html,js,css}=the Full Idea composer (task 1002928 / R152 of goal 1000062 — the five R141 fields with a live R142 completeness meter, both fetched from the server so the page carries no copy of the rules; drafts in localStorage so navigation costs nothing. Task 1002930 / R154 added the SPARK QUEUE above it and a second mode: `?develop=<id>` reuses the same form to develop a spark — the spark's words shown above the fields, its title read-only, and the file routed to POST /inbox/:id/develop instead of POST /inbox, under its own per-idea draft key); idea.html/idea.js=the idea-detail PAGE (/idea/:id, task 1002929 / R153 — the SECOND two-segment hall page after /task/:id: grade, the five answers in template order, the score, and both attributions. The GRADE decides what the page contains — a quick idea gets no five-field skeleton. task-detail.js keeps the #/idea/N overlay because ~320 of its lines are the Metic+ triage panel; the overlay now links here); profile.html/profile.js=the public, shareable builder profile page (/profile?id=N — rank, works, credits, karma, achievements; task 1467); profile-connections.js=section 6 of that page, the hub-only connections block (task 1002296 / R20 of goal 1000045 — the viewer-relative 1st/2nd/3rd/out-of-network label, the mutuals it was computed from, the connect button, and on your own profile both request queues plus your connections ledger. Loaded BEFORE profile.js, which calls window.OTBConnections.render behind a guard, so a page that omits the script simply has no section; the section reveals only when the hub's /connections routes answer, exactly as the cross-project record does. ADR 0212 records why it is on this page rather than the not-yet-built /u/&lt;handle&gt;, and why it addresses a builder by github_login and then uses the canonical handle the API echoes back); task.html/task.js=the task-detail page (/task/:id, task 751 — replaced the old #/task/N overlay); builders.js=the controller; dom-utils.js/hall-widgets.js=the widget+settings-panel registries.)
248
248
  │
249
249
  ├── infra/ ← ON-BOX scripts (task 1002699): the four reporter/heartbeat crons every dev box runs from its /workspace clone — box-heartbeat.sh (idle-sweep activity signal), box-report-version.sh (HEAD → POST /box/version), box-report-host-keys.sh (SSH host keys → POST /box/host-keys), box-report-terminal.sh (ttyd URL+credential → POST /box/terminal). Delivered by the workspace clone itself (modules/dev-box/box-access.js sparse scope includes infra/); crons self-installed by box-source-fetch.sh's per-tick bring-up. API base resolves GDS_BASE env → session-file api_base → skip (no hardcoded host in the neutral core).
250
250
  ├── devbox-app/ ← OTB Dev Box — Electron menu-bar/tray switch for the builder's box (#904, ADR 0045; internal). SELF-CONTAINED: own package.json (never feeds the droplet's npm ci); built by .github/workflows/build-devbox-app.yml; published to the public releases repo per config/devbox-app.json; release recipe docs/recipes/devbox-app-release.md
@@ -1289,5 +1289,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1289
1289
  landed since 1.19.424 with no explicit bump. run 33270559541. (task 1002620)
1290
1290
  1.19.426 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1291
1291
  landed since 1.19.425 with no explicit bump. run 33270772607. (task 1002620)
1292
+ 1.19.427 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1293
+ landed since 1.19.426 with no explicit bump. run 33271340372. (task 1002620)
1292
1294
  ---------------------------------------------------------------------------
1293
1295
  ```