@bongos/core 1.18.0 → 1.18.3
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 +132 -322
- package/README.md +1 -1
- package/config/branding.neutral.json +5 -1
- package/docs/adr/0142-project-context-is-prose-only.md +41 -0
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
- package/docs/adr/README.md +5 -2
- package/docs/api/openapi.json +1789 -450
- package/docs/api-reference.md +52 -5
- package/docs/file-map.md +7 -7
- package/docs/recipes/packaging-the-core.md +13 -0
- package/docs/recipes/upgrading-the-core.md +5 -2
- package/migrations/039_newcomer_friendly_column.sql +5 -0
- package/migrations/102_remove_box_first_provision_approval.sql +4 -0
- package/migrations/core_192_github_oauth_tokens.sql +33 -0
- package/modules/hall-ui/public/my-projects.html +78 -0
- package/modules/hall-ui/public/my-projects.js +142 -0
- package/modules/hall-ui/public/scouting.html +45 -0
- package/modules/hall-ui/public/scouting.js +73 -0
- package/modules/status-ui/public/explore.html +54 -0
- package/modules/status-ui/public/explore.js +67 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +211 -14
- package/scripts/gds/cli-lib.js +11 -5
- package/scripts/gds/lint-migrations.js +102 -0
- package/scripts/gds/login.js +42 -10
- package/scripts/gds/mirror-redact.js +5 -0
- package/scripts/gds/package-core.js +263 -4
- package/scripts/gds/provision.js +145 -7
- package/scripts/gds/publish-manifest.js +15 -0
- package/scripts/gds/run-unit-tests.js +38 -14
- package/scripts/gds/setup.js +41 -0
- package/scripts/gds/ship.js +115 -31
- package/scripts/gds/upgrade.js +147 -5
- package/scripts/migrate.sh +10 -0
- package/src/bongos/auth.js +362 -5
- package/src/bongos/db.js +66 -0
- package/src/bongos/github-repos.js +93 -0
- package/src/bongos/routes/auth.js +93 -11
- package/src/bongos/routes/github.js +90 -0
- package/src/bongos/routes/instance.js +15 -3
- package/src/bongos/routes.js +2 -0
- package/src/branding.js +17 -2
- package/src/instance-config.js +16 -0
- package/src/module-api.js +29 -1
- package/tests/branding.mjs +29 -23
- package/tests/github_repos_helpers.mjs +91 -0
- package/tests/github_token_store.mjs +121 -0
- package/tests/idp_device_exchange.mjs +105 -0
- package/tests/idp_federation.mjs +100 -0
- package/tests/migrations_forward_safe.mjs +86 -0
- package/tests/module_api.mjs +1 -1
- package/tests/package_core.mjs +246 -1
- package/tests/provision.mjs +157 -0
- package/tests/pruned_lockfile_integrity.mjs +79 -0
- package/tests/ship_ci_deploy.mjs +60 -0
- package/tests/upgrade.mjs +254 -5
- package/.gitignore +0 -91
- package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
- package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
- package/scripts/hall-preview/fixtures/access-requests.json +0 -20
- package/scripts/hall-preview/fixtures/achievements.json +0 -76
- package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
- package/scripts/hall-preview/fixtures/audit-log.json +0 -68
- package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
- package/scripts/hall-preview/fixtures/box__me.json +0 -5
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
- package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
- package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
- package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
- package/scripts/hall-preview/fixtures/goals.json +0 -502
- package/scripts/hall-preview/fixtures/goals__25.json +0 -96
- package/scripts/hall-preview/fixtures/goals__26.json +0 -67
- package/scripts/hall-preview/fixtures/goals__3.json +0 -103
- package/scripts/hall-preview/fixtures/goals__35.json +0 -49
- package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
- package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
- package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
- package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
- package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
- package/scripts/hall-preview/fixtures/inbox.json +0 -330
- package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
- package/scripts/hall-preview/fixtures/instance.json +0 -124
- package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
- package/scripts/hall-preview/fixtures/me.json +0 -476
- package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
- package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
- package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
- package/scripts/hall-preview/fixtures/me__render.json +0 -5
- package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
- package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
- package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
- package/scripts/hall-preview/fixtures/override-requests.json +0 -15
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
- package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
- package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
- package/scripts/hall-preview/fixtures/public__grades.json +0 -50
- package/scripts/hall-preview/fixtures/public__progress.json +0 -124
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
- package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
- package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
- package/scripts/hall-preview/fixtures/security__docs.json +0 -10
- package/scripts/hall-preview/fixtures/security__reports.json +0 -31
- package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
- package/scripts/hall-preview/fixtures/tasks.json +0 -2420
- package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
- package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
- package/scripts/hall-preview/fixtures/versions.json +0 -89
package/docs/api-reference.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# GDS API reference
|
|
4
4
|
|
|
5
|
-
> **Generated from the live route files** — the route file is authoritative.
|
|
5
|
+
> **Generated from the live route files** — the route file is authoritative. 245 endpoints across 41 route files.
|
|
6
6
|
> Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
|
|
7
7
|
|
|
8
8
|
Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
|
|
@@ -34,7 +34,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
34
34
|
|---|---|---|---|---|
|
|
35
35
|
| GET | `/api/bongos/audit-log` | `archon` | — | GET /api/gds/audit-log?limit=&offset=&builder_id=&method= Paginated, newest-first. |
|
|
36
36
|
|
|
37
|
-
## `auth` (
|
|
37
|
+
## `auth` (16)
|
|
38
38
|
|
|
39
39
|
| Method | Path | Rank | Body | Description |
|
|
40
40
|
|---|---|---|---|---|
|
|
@@ -43,6 +43,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
43
43
|
| POST | `/api/bongos/auth/app-pair/poll` | `public` | `poll_token` | poll with the secret poll_token; the token IS the bearer (mirrors /auth/device/poll, where the device_code is the bearer). |
|
|
44
44
|
| POST | `/api/bongos/auth/app-pair/start` | `public` | `device_name`, `platform` | starts a pairing; returns opaque codes only, no identity. |
|
|
45
45
|
| POST | `/api/bongos/auth/cli-token/issue` | `any-builder` | — | POST /auth/cli-token/issue — V3.R91 / task 401. |
|
|
46
|
+
| POST | `/api/bongos/auth/device/exchange` | `public` | `assertion` | POST /auth/device/exchange — CLI federation (ADR 0141 §1). |
|
|
46
47
|
| POST | `/api/bongos/auth/device/poll` | `public` | `device_code` | auth flow; the device code itself is the bearer. |
|
|
47
48
|
| POST | `/api/bongos/auth/device/start` | `public` | — | /auth/device — CLI flow rank: public — auth flow itself; identity establishment, not authorization. |
|
|
48
49
|
| GET | `/api/bongos/auth/discord/callback` | `any-builder` | — | completes the link to the caller's own identity. |
|
|
@@ -174,6 +175,13 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
174
175
|
| GET | `/api/bongos/gate-approvals` | `archon` | — | GET /gate-approvals — open PRs awaiting owner approval (gate-review red), with the verdict + per-file reasons + changed files + diff link. |
|
|
175
176
|
| POST | `/api/bongos/gate-approvals/:pr/approve` | `archon` | — | POST /gate-approvals/:pr/approve — record owner approval for a stuck gate PR and re-run the gate-review check so it clears. |
|
|
176
177
|
|
|
178
|
+
## `github` (2)
|
|
179
|
+
|
|
180
|
+
| Method | Path | Rank | Body | Description |
|
|
181
|
+
|---|---|---|---|---|
|
|
182
|
+
| GET | `/api/bongos/github/repos` | `any-builder` | — | acts only on the caller's OWN GitHub identity (the token they granted). |
|
|
183
|
+
| POST | `/api/bongos/github/repos` | `any-builder` | `name` | creates a repo in the caller's OWN account. |
|
|
184
|
+
|
|
177
185
|
## `goals` (24)
|
|
178
186
|
|
|
179
187
|
| Method | Path | Rank | Body | Description |
|
|
@@ -213,7 +221,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
213
221
|
|
|
214
222
|
| Method | Path | Rank | Body | Description |
|
|
215
223
|
|---|---|---|---|---|
|
|
216
|
-
| GET | `/api/bongos/healthz` | `public` | — | liveness probe; status.
|
|
224
|
+
| GET | `/api/bongos/healthz` | `public` | — | liveness probe; status.example.com hits this anonymously. |
|
|
217
225
|
|
|
218
226
|
## `inbox` (4)
|
|
219
227
|
|
|
@@ -303,6 +311,17 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
303
311
|
| POST | `/api/bongos/modules/:key/submit` | `metic+archon` | `signed_by` | POST /api/gds/modules/:key/submit — ADR 0107 §1 upstreaming submission. |
|
|
304
312
|
| GET | `/api/bongos/modules/submissions` | `metic+archon` | — | GET /api/gds/modules/submissions?status=&module_key= — the upstreaming review queue (task 1771 reads this). |
|
|
305
313
|
|
|
314
|
+
## `my-projects` (6)
|
|
315
|
+
|
|
316
|
+
| Method | Path | Rank | Body | Description |
|
|
317
|
+
|---|---|---|---|---|
|
|
318
|
+
| GET | `/api/bongos/my-projects` | `any-builder` | — | the caller's OWN cross-project memberships (own data). |
|
|
319
|
+
| POST | `/api/bongos/my-projects/:clientId/leave` | `any-builder` | — | leave a project from the caller's OWN membership list. |
|
|
320
|
+
| GET | `/api/bongos/my-projects/invites` | `any-builder` | — | the caller's OWN pending invitations (ADR 0141 §5). |
|
|
321
|
+
| POST | `/api/bongos/my-projects/invites/:clientId/accept` | `any-builder` | — | accept an invite (pending → builder) for the caller's OWN account. |
|
|
322
|
+
| POST | `/api/bongos/my-projects/invites/:clientId/decline` | `any-builder` | — | decline an invite (delete the pending row) for the caller's OWN account. |
|
|
323
|
+
| POST | `/api/bongos/my-projects/join` | `any-builder` | `origin` | request to join a project. |
|
|
324
|
+
|
|
306
325
|
## `override-requests` (2)
|
|
307
326
|
|
|
308
327
|
| Method | Path | Rank | Body | Description |
|
|
@@ -310,6 +329,16 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
310
329
|
| GET | `/api/bongos/override-requests` | `archon` | — | GET /override-requests?status=open — Archon queue. |
|
|
311
330
|
| POST | `/api/bongos/override-requests/:id/decide` | `archon` | `decision`, `note` | POST /override-requests/:id/decide — body: { decision: 'approve'\|'deny', note? } On approve, atomically flips the task completed→confirme… |
|
|
312
331
|
|
|
332
|
+
## `projects` (5)
|
|
333
|
+
|
|
334
|
+
| Method | Path | Rank | Body | Description |
|
|
335
|
+
|---|---|---|---|---|
|
|
336
|
+
| GET | `/api/bongos/projects` | `archon` | — | the full curation list (includes unfeatured + owner attribution). |
|
|
337
|
+
| POST | `/api/bongos/projects` | `archon` | `origin`, `name`, `tagline`, `art_url` | create / update a catalog entry (manual seed for hand-wired instances; federated projects auto-catalogue at registration). |
|
|
338
|
+
| PATCH | `/api/bongos/projects/:id/featured` | `archon` | `featured` | the feature / unfeature control (the ADR 0141 §5 curation gate). |
|
|
339
|
+
| GET | `/api/bongos/projects/featured` | `public` | — | a curated public showcase; no auth by design. |
|
|
340
|
+
| POST | `/api/bongos/projects/invite` | `archon` | `client_id`, `github_login` | invite a builder into a project (ADR 0141 §5, task #1002115). |
|
|
341
|
+
|
|
313
342
|
## `provisioning` (13)
|
|
314
343
|
|
|
315
344
|
| Method | Path | Rank | Body | Description |
|
|
@@ -349,7 +378,13 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
349
378
|
| GET | `/api/bongos/public/repo-info` | `public` | — | GitHub URL bases for the deployed checkout (V3.R13-followup-2 task 411). |
|
|
350
379
|
| GET | `/api/bongos/public/tasks/:id` | `public` | — | Single-task resolver for "#NNN" deep links. |
|
|
351
380
|
| GET | `/api/bongos/public/uptime` | `public` | — | Uptime: current status + 90-day history bucketed by hour. |
|
|
352
|
-
| GET | `/api/bongos/public/versions` | `public` | — | read-only surface for status.
|
|
381
|
+
| GET | `/api/bongos/public/versions` | `public` | — | read-only surface for status.example.com. |
|
|
382
|
+
|
|
383
|
+
## `scouting` (1)
|
|
384
|
+
|
|
385
|
+
| Method | Path | Rank | Body | Description |
|
|
386
|
+
|---|---|---|---|---|
|
|
387
|
+
| GET | `/api/bongos/scouting` | `archon` | — | the internal talent directory across every project. |
|
|
353
388
|
|
|
354
389
|
## `search` (2)
|
|
355
390
|
|
|
@@ -381,6 +416,18 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
381
416
|
| GET | `/api/bongos/sessions/mine` | `any-builder` | — | your OWN sessions (self-serve; no audit needed). |
|
|
382
417
|
| GET | `/api/bongos/sessions/search` | `archon` | — | cross-builder corpus search (BFG-principal posture until 6C.1). |
|
|
383
418
|
|
|
419
|
+
## `sso` (7)
|
|
420
|
+
|
|
421
|
+
| Method | Path | Rank | Body | Description |
|
|
422
|
+
|---|---|---|---|---|
|
|
423
|
+
| POST | `/api/bongos/sso/activity/rollup` | `public` | `client_id`, `client_secret`, `github_id`, `credits`, `tasks_shipped`, `karma`, `rank`, `disciplines` | POST /sso/activity/rollup — a federated instance reports a builder's activity snapshot (ADR 0141 §4). |
|
|
424
|
+
| GET | `/api/bongos/sso/authorize` | `public` | — | GET /sso/authorize?client_id=&redirect_uri=&state= — the browser authorize leg. |
|
|
425
|
+
| POST | `/api/bongos/sso/device/poll` | `public` | `device_code` | POST /sso/device/poll — poll the hub device flow. |
|
|
426
|
+
| POST | `/api/bongos/sso/device/start` | `public` | `origin` | POST /sso/device/start — begin CLI federation. |
|
|
427
|
+
| POST | `/api/bongos/sso/membership/check-in` | `public` | `client_id`, `client_secret`, `github_id`, `github_login`, `display_name`, `avatar_url`, `role` | POST /sso/membership/check-in — a federated instance reports, on every sign-in, that a builder builds on its project (ADR 0141 §3). |
|
|
428
|
+
| GET | `/api/bongos/sso/pubkey` | `public` | — | GET /sso/pubkey — the hub's Ed25519 assertion-verification key (public). |
|
|
429
|
+
| POST | `/api/bongos/sso/token` | `public` | `client_id`, `client_secret`, `code`, `redirect_uri` | POST /sso/token — server-to-server code redemption. |
|
|
430
|
+
|
|
384
431
|
## `tasks` (27)
|
|
385
432
|
|
|
386
433
|
| Method | Path | Rank | Body | Description |
|
|
@@ -419,7 +466,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
419
466
|
|---|---|---|---|---|
|
|
420
467
|
| GET | `/api/bongos/versions` | `public` | — | read-only version list; mirrors /public/versions. |
|
|
421
468
|
| POST | `/api/bongos/versions` | `archon` | `id`, `name`, `status`, `done_when`, `scope_doc_path`, `criteria` | POST /versions — create a version + its done-when criteria in one call. |
|
|
422
|
-
| GET | `/api/bongos/versions/:id/done-when` | `public` | — | Public: status.
|
|
469
|
+
| GET | `/api/bongos/versions/:id/done-when` | `public` | — | Public: status.example.com fetches this every 60s alongside other public/* surfaces. |
|
|
423
470
|
| POST | `/api/bongos/versions/:id/done-when` | `archon` | `criterion_id`, `criterion_md`, `goal_id`, `sort_order` | Auth (Archon): create a done-when criterion at RUNTIME. |
|
|
424
471
|
| GET | `/api/bongos/versions/:id/progress` | `public` | — | Per-criterion progress rollup: each done-when criterion → the tasks that gate it (task_criteria, migration 055) → live statuses → what re… |
|
|
425
472
|
| GET | `/api/bongos/versions/close-suggestions` | `public` | — | Close-flow gate suggestions for every version still 'building'. |
|
package/docs/file-map.md
CHANGED
|
@@ -322,7 +322,7 @@ tests/
|
|
|
322
322
|
├── builder-backup/SKILL.md ← /builder-backup — check DB backup status or trigger a fresh local dump before risky changes (migrations, destructive SQL); no SSH required
|
|
323
323
|
├── builder-box/SKILL.md ← /builder-box — show the builder their own dev box (state, IP/host, cost, SSH keys) and help them (re)connect
|
|
324
324
|
├── builder-claim/SKILL.md ← /builder-claim N — atomic claim; prints the claim-time context pack + discipline playbook routing
|
|
325
|
-
├── builder-connect/SKILL.md ← /builder-connect — one-command connect of the builder's Claude Desktop to their
|
|
325
|
+
├── builder-connect/SKILL.md ← /builder-connect — one-command connect of the builder's Claude Desktop to their Example dev box (ADR 0031)
|
|
326
326
|
├── builder-cost/SKILL.md ← /builder-cost — log a cost-ledger entry (api|compute|infra|domain|art|other)
|
|
327
327
|
├── builder-end/SKILL.md ← /builder-end — safely close out a session: resolve the claim (ship/release) + clean-git verify + worktree teardown + (on a dev box) offer to close the box; the bookend to /builder-start, and the own-scoped session close-out that /builder-exit redirects non-Archons to
|
|
328
328
|
├── builder-exit/SKILL.md ← /builder-exit — gracefully offboard (or reactivate) a builder: release claims, seal session logs, drop rank to xenos
|
|
@@ -336,20 +336,20 @@ tests/
|
|
|
336
336
|
├── builder-start/SKILL.md ← /builder-start — list claimable tasks (parallel-safe vs active claims)
|
|
337
337
|
├── close-box/SKILL.md ← /close-box — tear down the dev box you're working on, from inside the box terminal (POST /box/close)
|
|
338
338
|
├── design-sync/SKILL.md ← /design-sync — export repo design tokens + surfaces into Claude Design (token-preview bundle) and land generated UI code back as a GDS task (ADR 0081)
|
|
339
|
-
├── dev/SKILL.md ← /dev — engineering-session playbook for engineer-discipline tasks; the default understand→scoped-change→verify→ship loop made explicit ([#1272](https://
|
|
339
|
+
├── dev/SKILL.md ← /dev — engineering-session playbook for engineer-discipline tasks; the default understand→scoped-change→verify→ship loop made explicit ([#1272](https://example.com/builders#/task/1272))
|
|
340
340
|
├── feedback/SKILL.md ← /feedback — pull the latest Bongos feedback bundle (prompt.md + screenshot file paths) into a Claude Code session; backed by scripts/gds/feedback-latest.js (BV1.R13, ADR 0087)
|
|
341
341
|
├── figma-design-sync/SKILL.md ← sync UI-surface tokens/surfaces with Figma (push a frame plan, land a designer's Code Connect / MCP snapshot edit back as code) — the ADR 0081 human escape hatch; distinct from otb-figma-sync's pixel-art tile review (task 2048)
|
|
342
342
|
├── goal-review/SKILL.md ← /goal-review — walk the criterion review queue: each auto-flagged 'met — pending review' criterion gets confirm / reject / defer; confirming a goal's last criterion auto-achieves it (ADR 0086 §6); Metic+
|
|
343
343
|
├── idea-triage/SKILL.md ← /idea-triage — daily walk through the GDS idea_inbox (promote / discard / merge); Metic+
|
|
344
|
-
├── ideate/SKILL.md ← /ideate — ideation-session playbook for ideator-discipline tasks; inverts the loop (human drives, Claude is the sounding board) ([#1267](https://
|
|
344
|
+
├── ideate/SKILL.md ← /ideate — ideation-session playbook for ideator-discipline tasks; inverts the loop (human drives, Claude is the sounding board) ([#1267](https://example.com/builders#/task/1267))
|
|
345
345
|
├── merge-mode/SKILL.md ← /merge-mode — manual fallback for the auto-merge: land every confirmed task (merge + smoke + deploy)
|
|
346
|
-
├── new-project/SKILL.md ← /new-project — the canonical zero-to-live onboarding runbook for a new STANDALONE instance (name+address → init → scaffold → provision → OAuth+verified-secret+sign-in → verify → brand); the CLI + hall wizard are forms over it ([#1981](https://
|
|
346
|
+
├── new-project/SKILL.md ← /new-project — the canonical zero-to-live onboarding runbook for a new STANDALONE instance (name+address → init → scaffold → provision → OAuth+verified-secret+sign-in → verify → brand); the CLI + hall wizard are forms over it ([#1981](https://example.com/builders#/task/1981))
|
|
347
347
|
├── otb-character-review/SKILL.md ← /otb-character-review — score a painterly character ANIMATION (same character / anatomy / pose / smooth motion) — the animation sibling of otb-design-review (ADR 0088)
|
|
348
|
-
├── otb-design-review/SKILL.md ← /otb-design-review — score a tile against the
|
|
348
|
+
├── otb-design-review/SKILL.md ← /otb-design-review — score a tile against the Example rubric
|
|
349
349
|
├── otb-feedback-capture/SKILL.md ← /otb-feedback-capture — verbal design feedback → a durable style_guide.md rule
|
|
350
|
-
├── otb-figma-sync/SKILL.md ← /otb-figma-sync — push tiles into the
|
|
350
|
+
├── otb-figma-sync/SKILL.md ← /otb-figma-sync — push tiles into the Example Figma file as a review canvas
|
|
351
351
|
├── otb-tile-generate/SKILL.md ← /otb-tile-generate — run the pixel-art pipeline (generate world tiles/sprites)
|
|
352
|
-
├── paint/SKILL.md ← /paint — art-session playbook for artist-discipline tasks; show-first/low-text, drives the otb-* pixel-art pipeline underneath ([#1269](https://
|
|
352
|
+
├── paint/SKILL.md ← /paint — art-session playbook for artist-discipline tasks; show-first/low-text, drives the otb-* pixel-art pipeline underneath ([#1269](https://example.com/builders#/task/1269))
|
|
353
353
|
├── planning-session/SKILL.md ← /planning-session — run a structured planning session for an upcoming version (set criteria, brainstorm, rank, seed GDS); Metic+
|
|
354
354
|
├── priority-session/SKILL.md ← /priority-session — a trusted builder answers plain-speech questions; the answers reweight the idea_inbox + suggest what to build next
|
|
355
355
|
├── read-session-export/SKILL.md ← /read-session-export — read a Claude Code /export zip fast (prompts + thinking + tool calls → stdout); reads the existing export, doesn't make one. Backed by scripts/gds/read-session-export.js
|
|
@@ -26,12 +26,25 @@ Every file is **redacted** through the same fail-closed pipeline the public mirr
|
|
|
26
26
|
bongos package-core # from HEAD, version = platform CORE_VERSION
|
|
27
27
|
bongos package-core --ref origin/main --version 1.13.0
|
|
28
28
|
node scripts/gds/package-core.js --droplet-ip <ip> # add the prod IP as an exact gate literal
|
|
29
|
+
bongos package-core --self-verify # after building, prove the artifact reproduces its pin
|
|
30
|
+
bongos package-core --self-verify --self-verify-boot # ALSO npm-install the .tgz + boot /healthz
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
`--ref` chooses the source commit (default `HEAD`), `--out-dir` the output dir (default `dist`), `--version` overrides the stamped version. `stdout` carries a one-line JSON pin (`{ core_version, source_commit, tree_sha256, tarball }`) for CI/scripts to capture; human detail goes to `stderr`.
|
|
32
34
|
|
|
35
|
+
`--self-verify` (task [#1002218](https://example.com/builders#/task/1002218), audit H3) closes the produce-side of the integrity loop: after writing the artifact it **unpacks the built `.tgz` and recomputes `tree_sha256`, asserting it equals the manifest** (+ the on-disk `.tgz` bytes vs `tarball.sha256`) — proving the packager's own output reproduces its pin before it is trusted or published (a mismatch exits non-zero, ahead of the `--publish` leg). It is deterministic and network-free. Add `--self-verify-boot` to *also* `npm install` the tarball into a throwaway consumer and boot `/healthz` (reuses [`tests/consumer_layout_boot.mjs`](../../tests/consumer_layout_boot.mjs); needs npm/registry access — the deeper standing CI gate is task [#1002221](https://example.com/builders#/task/1002221)).
|
|
36
|
+
|
|
33
37
|
If the no-leak gate fails, it names the file + the residual literal. Fix the redaction rules ([`scripts/gds/mirror-redact.js`](../../scripts/gds/mirror-redact.js)) or the manifest exclusions ([`scripts/gds/publish-manifest.js`](../../scripts/gds/publish-manifest.js)) and re-run — do not weaken the gate to force a pass.
|
|
34
38
|
|
|
39
|
+
**The neutral floor** (task [#1002217](https://example.com/builders#/task/1002217), audit H4). Because the trunk carries no `config/branding.json`, the *branding-derived* redaction would strip nothing — so `package-core.js` always injects a fixed **`PRIOR_INSTANCE_FLOOR`** (prior-instance tokens/owners) into the doc-redaction + no-leak gate. Published **docs** are de-branded against it and the gate **fails closed** if a floor literal survives, even with no branding.json. **Functional** files still ship verbatim (the R88 fix) and are instead surfaced by the non-fatal `[leak-scan]` (task 1002220). To retire a new prior-instance token, add it to `PRIOR_INSTANCE_FLOOR` (kept in `package-core.js`, never in `mirror-redact.js` — that file's self-leak invariant forbids hardcoded literals).
|
|
40
|
+
|
|
41
|
+
## Prove it boots (the standing PROVE-PUBLISH gates)
|
|
42
|
+
|
|
43
|
+
Two standing gates keep "a published core boots clean from a bare install" honest (task [#1002221](https://example.com/builders#/task/1002221), audit H1/H2):
|
|
44
|
+
|
|
45
|
+
- **`node scripts/gds/run-unit-tests.js --integration`** — the integration-smoke gate. Runs the heavier integration set (longer per-test budget), including [`tests/consumer_layout_boot.mjs`](../../tests/consumer_layout_boot.mjs), which packages the core, `npm install`s the `.tgz`, and boots `/healthz` from the real consumer layout. Run it per release / on a schedule in an env that provides the services (network, and — for the deeper DB leg tracked as a follow-up — Postgres). The default `run-unit-tests.js` (no flag) stays the fast DB-free unit gate and *excludes* this set.
|
|
46
|
+
- **[`tests/pruned_lockfile_integrity.mjs`](../../tests/pruned_lockfile_integrity.mjs)** — a network-free unit guard (runs in the default gate every PR) that rebuilds the shipped pruned lockfile and asserts every registry package keeps a **well-formed, verbatim `integrity` hash**. This is the exact R88 regression (redaction once blanked those base64 hashes → `npm ci` could no longer verify) caught before a consumer's install ever fails.
|
|
47
|
+
|
|
35
48
|
## Consume + pin it (the R85 side)
|
|
36
49
|
|
|
37
50
|
A downstream instance repo:
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
1. **pre-flight** — module-compat vs the target core + a clean git tree (skip with `--force`).
|
|
10
10
|
2. **bump the pin** — vendor the new `bongos-core-<version>.tgz` and rewrite the `"@bongos/core": "file:vendor/…"` dependency.
|
|
11
11
|
3. **`npm install`** — `node_modules/@bongos/core` becomes the new version.
|
|
12
|
+
3b. **verify the integrity pin** (task [#1002216](https://example.com/builders#/task/1002216)) — recompute `tree_sha256` over the installed core and check the vendored `.tgz` bytes against `tarball.sha256`, comparing to the trusted reference manifest (the committed `vendor/…manifest.json`; the installed inner manifest for `--registry`). Runs **before** migrate so a corrupted core never touches the DB. A **mismatch is fatal → rollback**; a missing reference warns (fatal with `--require-pin`); opt out with `--skip-pin-verify`.
|
|
12
13
|
4. **`npm run migrate`** — the core's `migrate.sh` applies additive `core_*` migrations (merged three-root: core + instance + modules).
|
|
13
14
|
5. **materialize `.claude/`** — refresh skills/settings/hooks from the new core (`materializeClaude`), rewriting invocations to the `bongos` bin.
|
|
14
15
|
6. **restart** — `--service <unit>` or `--restart-cmd "<cmd>"`.
|
|
15
|
-
7. **verify** — installed version == target
|
|
16
|
-
8. **auto-rollback on failure** (task [#2149](https://example.com/builders#/task/2149), default ON) — if `npm install
|
|
16
|
+
7. **verify** — installed version == target + the **mandatory post-restart health check** (task [#1002222](https://example.com/builders#/task/1002222)): a bump that restarts the service **requires `--health-url <url>`** (it can't report success without confirming the new core serves). Opt out explicitly with `--no-health-check`; a bump with no managed restart needs no URL (it reports the new core installed-but-unconfirmed). A still-failing health poll fails the bump (auto-rollback).
|
|
17
|
+
8. **auto-rollback on failure** (task [#2149](https://example.com/builders#/task/2149), default ON) — if `npm install`/pin-verify/`migrate` fails or the step-7 health check fails, revert the pin → reinstall the previous core → restart → re-verify → write a `rolled_back:` ledger row. Opt out with `--no-rollback-on-failure`. See the section below.
|
|
17
18
|
|
|
18
19
|
Run bare (`bongos upgrade`, no `--to`) it stays the module-compat **pre-check** only.
|
|
19
20
|
|
|
@@ -63,6 +64,8 @@ By default (`--rollback-on-failure`, ON unless `--no-rollback-on-failure`) the c
|
|
|
63
64
|
|
|
64
65
|
**The DB is NOT rolled back — only the code pin.** Core migrations are additive + forward-safe (the core migration rule), so an already-applied migration is left in place; a *failed* migration is never recorded in `schema_migrations`, so a retry re-runs it cleanly. The reversal is recorded via the always-present `note` column (not a new `outcome` column) so it lands even in the failed-migrate path — where a newer column wouldn't yet exist on that instance.
|
|
65
66
|
|
|
67
|
+
> **The forward-safe rule is now enforced, not just documented** (task [#1002223](https://example.com/builders#/task/1002223), audit H10). A core migration that DROPs/RENAMEs/retypes existing state (which would strand the rolled-back old code on the new schema) fails the [`tests/migrations_forward_safe.mjs`](../../tests/migrations_forward_safe.mjs) unit gate unless it carries an explicit `-- forward-unsafe: <reason>` acknowledgement pragma. `migrate.sh` also prints a non-fatal notice at apply time (via [`scripts/gds/lint-migrations.js`](../../scripts/gds/lint-migrations.js)). Additive migrations (the norm) need no annotation.
|
|
68
|
+
|
|
66
69
|
**Proven live on demo.cloudbongos.com** (task [#2157](https://example.com/builders#/task/2157)): demo, on rollback-capable 1.17.3, was upgraded to a canary carrying a deliberately-failing migration. Migrate raised → the channel auto-reverted the pin to 1.17.3, reinstalled + restarted it, `/healthz` stayed 200 (**zero downtime** — the failure was at migrate, before the restart), and a `rolled_back` ledger row was written. This is the safety gate the unattended patch-only auto-upgrade subscription (task [#2150](https://example.com/builders#/task/2150)) depends on.
|
|
67
70
|
|
|
68
71
|
## Subscribing to updates — unattended patch-only auto-upgrade (task 2150 / ADR 0136)
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
-- 039_newcomer_friendly_column.sql — V3.R15: tasks.newcomer_friendly column +
|
|
2
2
|
-- view-level filter + Xenos claim-policy error code.
|
|
3
3
|
--
|
|
4
|
+
-- forward-unsafe: rename-column (RENAME tasks.xenos_claimable → newcomer_friendly). Historical
|
|
5
|
+
-- (V3.R15, 2026), already applied on every instance long before `bongos upgrade`'s code-only
|
|
6
|
+
-- rollback existed — no core version a current rollback could revert to reads the old column
|
|
7
|
+
-- name. Acknowledged per audit H10 / task 1002223 (scripts/gds/lint-migrations.js).
|
|
8
|
+
--
|
|
4
9
|
-- Context: V3.R15 (#239) was planned as the row-level "newcomer-friendly task"
|
|
5
10
|
-- gate for the Xenos rank. In parallel, #360 / ADR 0018 (migration 029) shipped
|
|
6
11
|
-- the same idea under the name `xenos_claimable`. Now V3.R15 closes by:
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
-- 102_remove_box_first_provision_approval.sql — ADR 0059 (task 1141): REMOVE the
|
|
2
2
|
-- dev-box "first-box Archon approval" gate. Reverts migration 071.
|
|
3
3
|
--
|
|
4
|
+
-- forward-unsafe: drop-column (box_intents.approved_by / approved_at). Historical (ADR 0059, task
|
|
5
|
+
-- 1141); the code that read those columns was removed in the SAME change, and it predates
|
|
6
|
+
-- `bongos upgrade`'s code-only rollback. Acknowledged per audit H10 / task 1002223.
|
|
7
|
+
--
|
|
4
8
|
-- Context
|
|
5
9
|
-- -------
|
|
6
10
|
-- Migration 071 (#717, ADR 0035 Decision 2) added a 'pending_approval' state to
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- core_192_github_oauth_tokens.sql — short-lived, encrypted GitHub repo tokens for
|
|
2
|
+
-- the onboarding wizard (task 1002099, ADR 0143).
|
|
3
|
+
--
|
|
4
|
+
-- The wizard (public-landing/create.html) opts into `public_repo` at GitHub sign-in
|
|
5
|
+
-- so it can LIST the signed-in user's repos (adopt) and CREATE an empty repo (fresh)
|
|
6
|
+
-- — things `read:user` cannot do. That user-to-server GitHub token is held here
|
|
7
|
+
-- BRIEFLY: one row per builder, the token encrypted with secret-box (master key in
|
|
8
|
+
-- server env only — a DB dump alone decrypts nothing), with a short expires_at (one
|
|
9
|
+
-- wizard sitting). It is read back by the repo picker/creator over the next few
|
|
10
|
+
-- minutes and deleted on use; getGithubToken lazily sweeps an expired row so the
|
|
11
|
+
-- table self-cleans without a cron. ON DELETE CASCADE ties a token to its builder.
|
|
12
|
+
--
|
|
13
|
+
-- This is the AUTH system's OWN handshake token (not a feature's third-party API
|
|
14
|
+
-- key like the art module's Gemini key, ADR 0102), so it lives in core. When
|
|
15
|
+
-- secret-box is unconfigured no row is ever written and the wizard falls back to
|
|
16
|
+
-- manual repo entry (ADR 0143).
|
|
17
|
+
--
|
|
18
|
+
-- Idempotent: safe to re-run.
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS github_oauth_tokens (
|
|
21
|
+
builder_id bigint PRIMARY KEY REFERENCES builders(id) ON DELETE CASCADE,
|
|
22
|
+
token_enc text NOT NULL,
|
|
23
|
+
scope text,
|
|
24
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
25
|
+
expires_at timestamptz NOT NULL
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
-- The read/sweep hot path filters on expiry.
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_github_oauth_tokens_expires
|
|
30
|
+
ON github_oauth_tokens (expires_at);
|
|
31
|
+
|
|
32
|
+
INSERT INTO schema_migrations (version) VALUES ('core_192_github_oauth_tokens')
|
|
33
|
+
ON CONFLICT (version) DO NOTHING;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>My Projects</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #fff7ed; --card: #ffffff; --ink: #2a1f17; --ink-soft: #6b5d52;
|
|
10
|
+
--rule: #fed7aa; --accent: #ea580c; --accent-deep: #c2410c;
|
|
11
|
+
}
|
|
12
|
+
* { box-sizing: border-box; }
|
|
13
|
+
body {
|
|
14
|
+
margin: 0; padding: 2.5rem 1.25rem 4rem; background: var(--bg); color: var(--ink);
|
|
15
|
+
font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.5;
|
|
16
|
+
}
|
|
17
|
+
.wrap { max-width: 820px; margin: 0 auto; }
|
|
18
|
+
h1 { font-size: 1.9rem; margin: 0 0 .3rem; }
|
|
19
|
+
.sub { color: var(--ink-soft); margin: 0 0 2rem; }
|
|
20
|
+
h2 { font-size: 1.1rem; margin: 2rem 0 .75rem; border-bottom: 1px solid var(--rule); padding-bottom: .35rem; }
|
|
21
|
+
.row {
|
|
22
|
+
display: flex; align-items: center; gap: .75rem; background: var(--card);
|
|
23
|
+
border: 1px solid var(--rule); border-radius: 12px; padding: .8rem 1rem; margin-bottom: .6rem;
|
|
24
|
+
}
|
|
25
|
+
.row .meta { flex: 1; min-width: 0; }
|
|
26
|
+
.row .name { font-weight: 600; }
|
|
27
|
+
.row .origin { color: var(--accent); font-size: .82rem; word-break: break-all; }
|
|
28
|
+
.row .owner-pill { font-size: .72rem; background: var(--accent); color: #fff; border-radius: 999px; padding: .1rem .55rem; }
|
|
29
|
+
button {
|
|
30
|
+
font: inherit; cursor: pointer; border: 1px solid var(--rule); background: #fff; color: var(--ink);
|
|
31
|
+
border-radius: 9px; padding: .4rem .8rem;
|
|
32
|
+
}
|
|
33
|
+
button:hover { border-color: var(--accent); color: var(--accent-deep); }
|
|
34
|
+
.join { display: flex; gap: .5rem; margin-top: .75rem; }
|
|
35
|
+
.join input { flex: 1; font: inherit; padding: .5rem .7rem; border: 1px solid var(--rule); border-radius: 9px; }
|
|
36
|
+
.empty { color: var(--ink-soft); }
|
|
37
|
+
.note { margin-top: .5rem; color: var(--ink-soft); font-size: .9rem; min-height: 1.2em; }
|
|
38
|
+
a.home { color: var(--accent); text-decoration: none; font-size: .9rem; }
|
|
39
|
+
@media (prefers-color-scheme: dark) {
|
|
40
|
+
:root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
|
|
41
|
+
button { background: #241c16; }
|
|
42
|
+
.join input { background: #241c16; color: inherit; }
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<div class="wrap">
|
|
48
|
+
<a class="home" href="/builders">← Back to the hall</a>
|
|
49
|
+
<h1>My Projects</h1>
|
|
50
|
+
<p class="sub">Every project you build on across the platform.</p>
|
|
51
|
+
|
|
52
|
+
<section>
|
|
53
|
+
<h2>Projects you own</h2>
|
|
54
|
+
<div id="owned"><p class="empty">—</p></div>
|
|
55
|
+
</section>
|
|
56
|
+
|
|
57
|
+
<section id="invites-section" hidden>
|
|
58
|
+
<h2>Invitations</h2>
|
|
59
|
+
<div id="invites"></div>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section>
|
|
63
|
+
<h2>Projects you build on</h2>
|
|
64
|
+
<div id="building"><p class="empty">—</p></div>
|
|
65
|
+
</section>
|
|
66
|
+
|
|
67
|
+
<section>
|
|
68
|
+
<h2>Join a project</h2>
|
|
69
|
+
<div class="join">
|
|
70
|
+
<input id="join-origin" type="url" placeholder="https://project.cloudbongos.com" autocomplete="off">
|
|
71
|
+
<button id="join-btn" type="button">Request to join</button>
|
|
72
|
+
</div>
|
|
73
|
+
<p id="join-note" class="note"></p>
|
|
74
|
+
</section>
|
|
75
|
+
</div>
|
|
76
|
+
<script src="/my-projects.js"></script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// my-projects.js — the signed-in builder's cross-project view (ADR 0141 §3, task
|
|
2
|
+
// #1002112). Fetches /api/gds/my-projects (session cookie, same-origin) and renders
|
|
3
|
+
// owned + building-on lists with self-service leave, plus a join-by-origin form.
|
|
4
|
+
// All network values are rendered via textContent — never innerHTML.
|
|
5
|
+
(function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
var ownedEl = document.getElementById('owned');
|
|
8
|
+
var buildingEl = document.getElementById('building');
|
|
9
|
+
var joinBtn = document.getElementById('join-btn');
|
|
10
|
+
var joinInput = document.getElementById('join-origin');
|
|
11
|
+
var joinNote = document.getElementById('join-note');
|
|
12
|
+
|
|
13
|
+
function el(tag, cls) { var e = document.createElement(tag); if (cls) e.className = cls; return e; }
|
|
14
|
+
|
|
15
|
+
function safeOriginHost(origin) {
|
|
16
|
+
try {
|
|
17
|
+
var u = new URL(String(origin));
|
|
18
|
+
return (u.protocol === 'https:' || u.protocol === 'http:') ? u.host : null;
|
|
19
|
+
} catch (_) { return null; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderList(container, items, opts) {
|
|
23
|
+
container.textContent = '';
|
|
24
|
+
if (!items || items.length === 0) {
|
|
25
|
+
var p = el('p', 'empty');
|
|
26
|
+
p.textContent = opts.emptyText;
|
|
27
|
+
container.appendChild(p);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
items.forEach(function (m) {
|
|
31
|
+
var row = el('div', 'row');
|
|
32
|
+
var meta = el('div', 'meta');
|
|
33
|
+
var name = el('div', 'name');
|
|
34
|
+
name.textContent = m.name || m.client_id;
|
|
35
|
+
meta.appendChild(name);
|
|
36
|
+
var host = safeOriginHost(m.origin);
|
|
37
|
+
if (host) {
|
|
38
|
+
var o = el('div', 'origin');
|
|
39
|
+
o.textContent = host;
|
|
40
|
+
meta.appendChild(o);
|
|
41
|
+
}
|
|
42
|
+
row.appendChild(meta);
|
|
43
|
+
if (m.role === 'owner') {
|
|
44
|
+
var pill = el('span', 'owner-pill');
|
|
45
|
+
pill.textContent = 'owner';
|
|
46
|
+
row.appendChild(pill);
|
|
47
|
+
}
|
|
48
|
+
if (opts.leavable) {
|
|
49
|
+
var btn = el('button');
|
|
50
|
+
btn.type = 'button';
|
|
51
|
+
btn.textContent = 'Leave';
|
|
52
|
+
btn.addEventListener('click', function () { leave(m.client_id, row, btn); });
|
|
53
|
+
row.appendChild(btn);
|
|
54
|
+
}
|
|
55
|
+
container.appendChild(row);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function leave(clientId, row, btn) {
|
|
60
|
+
btn.disabled = true;
|
|
61
|
+
btn.textContent = 'Leaving…';
|
|
62
|
+
fetch('/api/gds/my-projects/' + encodeURIComponent(clientId) + '/leave', { method: 'POST' })
|
|
63
|
+
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
64
|
+
.then(function () { row.remove(); })
|
|
65
|
+
.catch(function () { btn.disabled = false; btn.textContent = 'Leave'; });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveInvite(clientId, accept, row, btns) {
|
|
69
|
+
btns.forEach(function (b) { b.disabled = true; });
|
|
70
|
+
fetch('/api/gds/my-projects/invites/' + encodeURIComponent(clientId) + '/' + (accept ? 'accept' : 'decline'), { method: 'POST' })
|
|
71
|
+
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
72
|
+
.then(function () { row.remove(); if (accept) load(); loadInvites(); })
|
|
73
|
+
.catch(function () { btns.forEach(function (b) { b.disabled = false; }); });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function loadInvites() {
|
|
77
|
+
var section = document.getElementById('invites-section');
|
|
78
|
+
var container = document.getElementById('invites');
|
|
79
|
+
fetch('/api/gds/my-projects/invites', { headers: { Accept: 'application/json' } })
|
|
80
|
+
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
81
|
+
.then(function (data) {
|
|
82
|
+
var invites = (data && data.invites) || [];
|
|
83
|
+
if (invites.length === 0) { section.hidden = true; return; }
|
|
84
|
+
container.textContent = '';
|
|
85
|
+
invites.forEach(function (inv) {
|
|
86
|
+
var row = el('div', 'row');
|
|
87
|
+
var meta = el('div', 'meta');
|
|
88
|
+
var name = el('div', 'name');
|
|
89
|
+
name.textContent = inv.name || inv.client_id;
|
|
90
|
+
meta.appendChild(name);
|
|
91
|
+
var host = safeOriginHost(inv.origin);
|
|
92
|
+
if (host) { var o = el('div', 'origin'); o.textContent = host; meta.appendChild(o); }
|
|
93
|
+
row.appendChild(meta);
|
|
94
|
+
var accept = el('button'); accept.type = 'button'; accept.textContent = 'Accept';
|
|
95
|
+
var decline = el('button'); decline.type = 'button'; decline.textContent = 'Decline';
|
|
96
|
+
accept.addEventListener('click', function () { resolveInvite(inv.client_id, true, row, [accept, decline]); });
|
|
97
|
+
decline.addEventListener('click', function () { resolveInvite(inv.client_id, false, row, [accept, decline]); });
|
|
98
|
+
row.appendChild(accept); row.appendChild(decline);
|
|
99
|
+
container.appendChild(row);
|
|
100
|
+
});
|
|
101
|
+
section.hidden = false;
|
|
102
|
+
})
|
|
103
|
+
.catch(function () { section.hidden = true; });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function load() {
|
|
107
|
+
fetch('/api/gds/my-projects', { headers: { Accept: 'application/json' } })
|
|
108
|
+
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
109
|
+
.then(function (data) {
|
|
110
|
+
renderList(ownedEl, data.owned, { emptyText: 'You don’t own any projects yet.', leavable: false });
|
|
111
|
+
renderList(buildingEl, data.building, { emptyText: 'You’re not building on any other projects yet.', leavable: true });
|
|
112
|
+
})
|
|
113
|
+
.catch(function () {
|
|
114
|
+
ownedEl.textContent = ''; buildingEl.textContent = '';
|
|
115
|
+
var p = el('p', 'empty'); p.textContent = 'Couldn’t load your projects right now.';
|
|
116
|
+
buildingEl.appendChild(p);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
joinBtn.addEventListener('click', function () {
|
|
121
|
+
var origin = (joinInput.value || '').trim();
|
|
122
|
+
if (!origin) { joinNote.textContent = 'Enter a project URL.'; return; }
|
|
123
|
+
joinBtn.disabled = true;
|
|
124
|
+
joinNote.textContent = 'Requesting…';
|
|
125
|
+
fetch('/api/gds/my-projects/join', {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: { 'Content-Type': 'application/json' },
|
|
128
|
+
body: JSON.stringify({ origin: origin }),
|
|
129
|
+
})
|
|
130
|
+
.then(function (r) { return r.json().then(function (b) { return { ok: r.ok, body: b }; }); })
|
|
131
|
+
.then(function (res) {
|
|
132
|
+
joinBtn.disabled = false;
|
|
133
|
+
if (res.ok && res.body && res.body.message) { joinNote.textContent = res.body.message; joinInput.value = ''; load(); }
|
|
134
|
+
else if (res.body && res.body.error && res.body.error.code === 'unknown_project') { joinNote.textContent = 'That project isn’t on the platform.'; }
|
|
135
|
+
else { joinNote.textContent = 'Could not request access right now.'; }
|
|
136
|
+
})
|
|
137
|
+
.catch(function () { joinBtn.disabled = false; joinNote.textContent = 'Could not request access right now.'; });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
load();
|
|
141
|
+
loadInvites();
|
|
142
|
+
}());
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Scouting — cross-project builders</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #fff7ed; --card: #ffffff; --ink: #2a1f17; --ink-soft: #6b5d52;
|
|
10
|
+
--rule: #fed7aa; --accent: #ea580c;
|
|
11
|
+
}
|
|
12
|
+
* { box-sizing: border-box; }
|
|
13
|
+
body {
|
|
14
|
+
margin: 0; padding: 2.5rem 1.25rem 4rem; background: var(--bg); color: var(--ink);
|
|
15
|
+
font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.5;
|
|
16
|
+
}
|
|
17
|
+
.wrap { max-width: 960px; margin: 0 auto; }
|
|
18
|
+
a.home { color: var(--accent); text-decoration: none; font-size: .9rem; }
|
|
19
|
+
h1 { font-size: 1.9rem; margin: .4rem 0 .3rem; }
|
|
20
|
+
.sub { color: var(--ink-soft); margin: 0 0 1.75rem; }
|
|
21
|
+
.badge { display: inline-block; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
|
|
22
|
+
background: var(--accent); color: #fff; border-radius: 999px; padding: .12rem .6rem; vertical-align: middle; }
|
|
23
|
+
table { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--rule); border-radius: 12px; overflow: hidden; }
|
|
24
|
+
th, td { text-align: left; padding: .6rem .8rem; border-bottom: 1px solid var(--rule); font-size: .92rem; }
|
|
25
|
+
th { color: var(--ink-soft); font-weight: 600; }
|
|
26
|
+
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
27
|
+
tr:last-child td { border-bottom: none; }
|
|
28
|
+
.who { font-weight: 600; }
|
|
29
|
+
.note { color: var(--ink-soft); padding: 2.5rem 1rem; text-align: center; }
|
|
30
|
+
.scroll { overflow-x: auto; }
|
|
31
|
+
@media (prefers-color-scheme: dark) {
|
|
32
|
+
:root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body>
|
|
37
|
+
<div class="wrap">
|
|
38
|
+
<a class="home" href="/builders">← Back to the hall</a>
|
|
39
|
+
<h1>Scouting <span class="badge">Archon only</span></h1>
|
|
40
|
+
<p class="sub">Builders active across all hosted projects — an internal talent view. Descriptive only; it grants no authority anywhere.</p>
|
|
41
|
+
<div id="body" class="note">Loading…</div>
|
|
42
|
+
</div>
|
|
43
|
+
<script src="/scouting.js"></script>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// scouting.js — the internal Archon-only cross-project scouting view (ADR 0141 §4,
|
|
2
|
+
// task #1002114). Fetches /api/gds/scouting (Archon-gated server-side) and renders a
|
|
3
|
+
// ranked table. A non-Archon gets a 403 and sees the Archon-only notice — the data
|
|
4
|
+
// endpoint's rank gate is the real protection ("NOT shown to builders"). All network
|
|
5
|
+
// values are rendered via textContent — never innerHTML.
|
|
6
|
+
(function () {
|
|
7
|
+
'use strict';
|
|
8
|
+
var body = document.getElementById('body');
|
|
9
|
+
|
|
10
|
+
function el(tag, cls) { var e = document.createElement(tag); if (cls) e.className = cls; return e; }
|
|
11
|
+
function td(text, cls) { var c = el('td', cls); c.textContent = text; return c; }
|
|
12
|
+
|
|
13
|
+
function fmtRecency(iso) {
|
|
14
|
+
if (!iso) return '—';
|
|
15
|
+
var t = Date.parse(iso);
|
|
16
|
+
if (isNaN(t)) return '—';
|
|
17
|
+
var days = Math.floor((Date.now() - t) / 86400000);
|
|
18
|
+
if (days <= 0) return 'today';
|
|
19
|
+
if (days === 1) return '1 day ago';
|
|
20
|
+
if (days < 30) return days + ' days ago';
|
|
21
|
+
return Math.floor(days / 30) + ' mo ago';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function render(builders) {
|
|
25
|
+
body.className = '';
|
|
26
|
+
body.textContent = '';
|
|
27
|
+
if (!builders || builders.length === 0) {
|
|
28
|
+
body.className = 'note';
|
|
29
|
+
body.textContent = 'No cross-project activity reported yet.';
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
var scroll = el('div', 'scroll');
|
|
33
|
+
var table = el('table');
|
|
34
|
+
var thead = el('thead');
|
|
35
|
+
var hr = el('tr');
|
|
36
|
+
[['Builder', ''], ['Projects', 'num'], ['Credits', 'num'], ['Ships', 'num'], ['Karma', 'num'], ['Disciplines', ''], ['Last active', '']]
|
|
37
|
+
.forEach(function (h) { var th = el('th', h[1]); th.textContent = h[0]; hr.appendChild(th); });
|
|
38
|
+
thead.appendChild(hr); table.appendChild(thead);
|
|
39
|
+
var tbody = el('tbody');
|
|
40
|
+
builders.forEach(function (b) {
|
|
41
|
+
var tr = el('tr');
|
|
42
|
+
var who = el('td');
|
|
43
|
+
var name = el('span', 'who');
|
|
44
|
+
name.textContent = b.github_login || b.display_name || ('#' + b.github_id);
|
|
45
|
+
who.appendChild(name);
|
|
46
|
+
tr.appendChild(who);
|
|
47
|
+
tr.appendChild(td(String(b.project_count != null ? b.project_count : '—'), 'num'));
|
|
48
|
+
tr.appendChild(td(String(b.total_credits != null ? b.total_credits : 0), 'num'));
|
|
49
|
+
tr.appendChild(td(String(b.total_tasks_shipped != null ? b.total_tasks_shipped : 0), 'num'));
|
|
50
|
+
tr.appendChild(td(String(b.total_karma != null ? b.total_karma : 0), 'num'));
|
|
51
|
+
tr.appendChild(td(Array.isArray(b.disciplines) ? b.disciplines.join(', ') : '—'));
|
|
52
|
+
tr.appendChild(td(fmtRecency(b.last_active)));
|
|
53
|
+
tbody.appendChild(tr);
|
|
54
|
+
});
|
|
55
|
+
table.appendChild(tbody);
|
|
56
|
+
scroll.appendChild(table);
|
|
57
|
+
body.appendChild(scroll);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fetch('/api/gds/scouting', { headers: { Accept: 'application/json' } })
|
|
61
|
+
.then(function (r) {
|
|
62
|
+
if (r.status === 403 || r.status === 401) { throw { archon: true }; }
|
|
63
|
+
if (!r.ok) throw new Error('http ' + r.status);
|
|
64
|
+
return r.json();
|
|
65
|
+
})
|
|
66
|
+
.then(function (data) { render(data && data.builders); })
|
|
67
|
+
.catch(function (e) {
|
|
68
|
+
body.className = 'note';
|
|
69
|
+
body.textContent = (e && e.archon)
|
|
70
|
+
? 'This view is Archon-only.'
|
|
71
|
+
: 'Couldn’t load the scouting view right now.';
|
|
72
|
+
});
|
|
73
|
+
}());
|