@bongos/core 1.19.871 → 1.19.872
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 +163 -93
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +5 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +10 -0
- package/docs/api/openapi.json +61 -3
- package/docs/api-reference.md +7 -1
- package/docs/architecture.md +7 -7
- package/docs/branding-contract.md +1 -1
- package/docs/copy-inventory.md +190 -189
- package/docs/copy-registry.json +264 -255
- package/docs/design/hall-direction-v2.md +2 -2
- package/docs/module-api-changelog.md +2 -0
- package/docs/onboarding/diagrams/README.md +4 -4
- package/docs/onboarding/drachmae-and-karma.md +2 -2
- package/docs/onboarding/primer.md +1 -1
- package/docs/recipes/README.md +1 -1
- package/docs/recipes/bongos-cli-release.md +4 -4
- package/docs/recipes/core-release-pipeline.md +3 -3
- package/docs/recipes/instance-deploy.md +2 -2
- package/docs/recipes/ops-gotchas.md +2 -2
- package/docs/recipes/search-index-ingestion.md +2 -2
- package/docs/recipes/upgrading-the-core.md +1 -1
- package/modules/autonomy/gauge.js +218 -0
- package/modules/dev-box/app/README.md +1 -1
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
- package/modules/hall-ui/public/blockers.js +138 -34
- package/modules/hall-ui/public/builders.js +2 -1
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/collab.js +6 -3
- package/modules/hall-ui/public/dom-utils.js +46 -5
- package/modules/hall-ui/public/goals-page.js +7 -3
- package/modules/hall-ui/public/goals-render.js +4 -3
- package/modules/hall-ui/public/hall-render.js +1 -1
- package/modules/hall-ui/public/people.js +3 -3
- package/modules/hall-ui/public/profile-charts.js +5 -2
- package/modules/hall-ui/public/profile.html +3 -3
- package/modules/hall-ui/public/profile.js +6 -6
- package/modules/hall-ui/public/settings-specialities.js +2 -2
- package/modules/hall-ui/public/settings.html +4 -4
- package/modules/hall-ui/public/settings.js +2 -2
- package/modules/hall-ui/public/shell.js +70 -0
- package/modules/hall-ui/public/style.css +31 -0
- package/modules/hall-ui/public/work.js +12 -14
- package/modules/provisioning/module.json +3 -3
- package/modules/public-landing/public/terms.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/autobongos-loop.js +170 -0
- package/scripts/gds/autobongos-run.js +317 -0
- package/scripts/gds/autobongos-verify.js +377 -0
- package/scripts/gds/autonomy-gauge-statusline.js +124 -0
- package/scripts/gds/autonomy-gauge.js +84 -0
- package/scripts/gds/run-unit-tests.js +120 -2
- package/scripts/gds/sequence.js +3 -0
- package/scripts/gds/start.js +16 -10
- package/src/bongos/core-update.js +147 -0
- package/src/bongos/routes/core-update.js +49 -0
- package/src/bongos/routes.js +2 -0
- package/src/module-api.js +1 -1
- package/tests/account_privacy_flags.mjs +3 -1
- package/tests/autobongos_loop.mjs +446 -0
- package/tests/autobongos_verify.mjs +325 -0
- package/tests/autonomy_gauge.mjs +344 -0
- package/tests/blockers_hall.mjs +20 -6
- package/tests/blockers_kit_pager.mjs +260 -0
- package/tests/blockers_page_ui.mjs +5 -4
- package/tests/box_ship_permissions.mjs +30 -0
- package/tests/core_update_banner.mjs +308 -0
- package/tests/eviction_policy.mjs +22 -1
- package/tests/git_guard.mjs +68 -0
- package/tests/goal_criterion_confirm_ui.mjs +7 -0
- package/tests/goal_invite_consent.mjs +7 -0
- package/tests/goal_sort_staleness.mjs +6 -2
- package/tests/hall_board_world.mjs +4 -1
- package/tests/hall_builders_page_pass.mjs +7 -0
- package/tests/hall_landing_boot.mjs +7 -0
- package/tests/hall_profile_world.mjs +20 -2
- package/tests/hall_record_world.mjs +7 -0
- package/tests/hall_roster_world.mjs +2 -1
- package/tests/hall_studio_world.mjs +7 -0
- package/tests/hook_shell_tools.mjs +23 -1
- package/tests/idea_detail_page.mjs +7 -0
- package/tests/idea_spark_hall.mjs +7 -0
- package/tests/integration_parked_ratchet.mjs +79 -0
- package/tests/module_contributions.mjs +134 -8
- package/tests/people_directory_clip.mjs +7 -0
- package/tests/profile_activity.mjs +7 -0
- package/tests/profile_ui_cross_project.mjs +9 -1
- package/tests/provision_disconnect.mjs +7 -0
- package/tests/provision_restart.mjs +7 -0
- package/tests/provision_settings_apply.mjs +7 -0
- package/tests/sessions_kit_pager.mjs +7 -0
- package/tests/watch_applications_queue.mjs +7 -0
- package/tests/watch_roster_self_row.mjs +7 -0
- package/tests/watch_sealed_floor.mjs +7 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# The builders' hall, direction v2: five archetypes of the chrome world (task 1003302)
|
|
2
2
|
|
|
3
3
|
**Date:** 2026-08-30 · **Goal:** 1000081 (Builders' hall in the world) · **Criterion:** `hall-archetypes-approved` ·
|
|
4
|
-
**World:** the chrome world, recorded in `DESIGN.md` at the repo root by task 1003298 ([ADR 0196](adr/0196-chrome-world-token-contract-and-the-landing-sheets.md), [ADR 0204](adr/0204-the-apex-is-dark-only.md)).
|
|
4
|
+
**World:** the chrome world, recorded in `DESIGN.md` at the repo root by task 1003298 ([ADR 0196](../adr/0196-chrome-world-token-contract-and-the-landing-sheets.md), [ADR 0204](../adr/0204-the-apex-is-dark-only.md)).
|
|
5
5
|
|
|
6
6
|
This document records the hall's composition round: five archetype pages, three versions each, built as real
|
|
7
7
|
HTML against the world the apex already lives in. It is the round that answers *how the hall should look*.
|
|
@@ -177,7 +177,7 @@ does differently, and its honest risk.
|
|
|
177
177
|
4. **Two regions on the task page do not exist today.** This round's brief asked for a grade panel and a session
|
|
178
178
|
log; grades live on Standing and session logs on `/sessions`. The task mocks show both marked `proposed`, with
|
|
179
179
|
a dashed edge and a mono eyebrow. Say whether the build adds them or leaves them where they are.
|
|
180
|
-
5. **The light mode.** The apex went dark only ([ADR 0204](adr/0204-the-apex-is-dark-only.md)), so the hall is now
|
|
180
|
+
5. **The light mode.** The apex went dark only ([ADR 0204](../adr/0204-the-apex-is-dark-only.md)), so the hall is now
|
|
181
181
|
the only surface with a real light mode. Confirm it stays, since it doubles the build's contrast work.
|
|
182
182
|
|
|
183
183
|
**If no pick lands, the build takes v2 "the sheet" for all five archetypes**, as the version that reads the
|
|
@@ -2216,5 +2216,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2216
2216
|
landed since 1.19.869 with no explicit bump. run 35388809554. (task 1002620)
|
|
2217
2217
|
1.19.871 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2218
2218
|
landed since 1.19.870 with no explicit bump. run 35393164740. (task 1002620)
|
|
2219
|
+
1.19.872 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2220
|
+
landed since 1.19.871 with no explicit bump. run 35419318693. (task 1002620)
|
|
2219
2221
|
---------------------------------------------------------------------------
|
|
2220
2222
|
```
|
|
@@ -11,7 +11,7 @@ Five flowcharts that explain Bongos to a new builder at a glance. Each diagram i
|
|
|
11
11
|
| Diagram | What drives its state |
|
|
12
12
|
|---|---|
|
|
13
13
|
| `01-task-lifecycle` · `02-rank-ladder` · `05-versioning` (**mechanical**) | The task-status set + rank enum/wiring (read from `migrations/` + `src/bongos/`) and the live version set (production API). No auth needed — always regenerate. |
|
|
14
|
-
| `03-
|
|
14
|
+
| `03-drachmae-karma` · `04-architecture` (**conceptual**) | Each feature-piece is **LIVE iff its backing Bongos task is `shipped`**, plus 03's "N seeded" count from the achievements seed in `migrations/`. These read the **authenticated** `/tasks/:id` status, so when no Bongos session is available (headless run, no creds) the generator **skips** 03/04 and leaves them untouched rather than emit wrong data. (04's device/box/server/GitHub *topology* is structural; only its LIVE/PLANNED markers — the memory chain + the box→prod ship-flow legs — are task-gated.) |
|
|
15
15
|
|
|
16
16
|
The mechanical diagrams regenerate **on every deploy** (`ship.js` / `/merge-mode` run `gen-diagrams.js` before deploying; a changed diagram is committed and shipped automatically). The conceptual diagrams regenerate the same way whenever a session is present (the deploy host has one). The `diagram-drift` routine remains as a belt-and-braces backstop. So `/builders/diagrams` is never more than one deploy behind the live architecture.
|
|
17
17
|
|
|
@@ -42,11 +42,11 @@ The 16-value reserved rank enum (migration 023) in three Greek bands, which rank
|
|
|
42
42
|
Source: [`02-rank-ladder.mmd`](02-rank-ladder.mmd)
|
|
43
43
|
|
|
44
44
|
### 3. Example & karma — sourcing and spending
|
|
45
|
-
Where each comes from and where each goes. Key facts: **example = credits** (same thing, in-world name), they are **equity, not a spendable currency**, and the **primary source is now a cost-plus reward on each shipped session** — `round(true_cost × 1.20)` ([ADR 0054](../../adr/0054-session-token-reward-cost-plus.md)) — alongside per-task `credits_reward × kind multiplier` ([ADR 0023](../../adr/0023-kind-multipliers-and-idea-bonus.md)), achievements, and the (rate-card-live) security bounty. Karma is a separate peer-trust signal that does **not** gate rank promotions; its LIVE/PLANNED state tracks its backing tasks (R25/R32/R26). The full write-up is [`docs/onboarding/example-and-karma.md`](../
|
|
45
|
+
Where each comes from and where each goes. Key facts: **example = credits** (same thing, in-world name), they are **equity, not a spendable currency**, and the **primary source is now a cost-plus reward on each shipped session** — `round(true_cost × 1.20)` ([ADR 0054](../../adr/0054-session-token-reward-cost-plus.md)) — alongside per-task `credits_reward × kind multiplier` ([ADR 0023](../../adr/0023-kind-multipliers-and-idea-bonus.md)), achievements, and the (rate-card-live) security bounty. Karma is a separate peer-trust signal that does **not** gate rank promotions; its LIVE/PLANNED state tracks its backing tasks (R25/R32/R26). The full write-up is [`docs/onboarding/example-and-karma.md`](../drachmae-and-karma.md).
|
|
46
46
|
|
|
47
|
-

|
|
48
48
|
|
|
49
|
-
Source: [`03-
|
|
49
|
+
Source: [`03-drachmae-karma.mmd`](03-drachmae-karma.mmd)
|
|
50
50
|
|
|
51
51
|
### 4. The complete architecture — where your work runs and how code reaches production
|
|
52
52
|
One map of the whole system, merging the old "server ↔ repo ↔ memory" three-layer view with the dev-box → production ship flow. The four places Claude Code splits across — your **device** (screen only) · the **dev box** (the worker, the code, *and* your local memory dir) · the **server/droplet** (authority *and* production: Postgres, the server-side memory store, the push credential) · **GitHub** (branch-protected main + Actions) — plus two flows woven through it: the **per-builder memory layer** (ADR 0024 — local ↔ server, sync-on-ship / pull-on-start) and the **box → prod ship path** (a credential-less box re-auths and uploads its branch as a git bundle; the server pushes it with a GitHub App credential; the PR auto-merges and GitHub Actions deploys — no droplet key on any box). Every feature-piece + flow edge is LIVE/solid iff its backing task is `shipped`. Backing tasks: memory chain [#273](https://example.com/builders#/task/273)–[#276](https://example.com/builders#/task/276); dev box [#904](https://example.com/builders#/task/904)/[#774](https://example.com/builders#/task/774)/[#897](https://example.com/builders#/task/897)/[#919](https://example.com/builders#/task/919); box→prod [#1025](https://example.com/builders#/task/1025)/[#859](https://example.com/builders#/task/859)/[#1028](https://example.com/builders#/task/1028)/[#1027](https://example.com/builders#/task/1027) (all shipped → the map reads all-LIVE today). See [ADR 0024](../../adr/0024-cloneable-repo-local-first-memory.md) (cloneable memory), [ADR 0055](../../adr/0055-server-mediated-branch-publish.md) (server-mediated publish), [ADR 0053](../../adr/0053-scoped-dev-box-session.md) (scoped box token), [ADR 0042](../../adr/0042-builder-self-deploy-ci-auto-merge.md) (CI self-deploy), [ADR 0016](../../adr/0016-trust-boundary-server-enforced-permissions.md) (trust boundary).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
*The complete reference for the two reputation tracks in Bongos. The [primer](primer.md) gives you the one-paragraph version; this is the full picture, kept honest against the live system. ~8 minutes.*
|
|
4
4
|
|
|
5
|
-
> **Source of truth.** The numbers live in the production database (`builders.total_credits`, `builders.karma`) and the rules live in code (`src/bongos/db.js`, `src/bongos/cost.js`). This doc *describes* them; where they disagree, the code wins. The two diagrams that visualize this — [task lifecycle](diagrams/01-task-lifecycle.png) and [example & karma](diagrams/03-
|
|
5
|
+
> **Source of truth.** The numbers live in the production database (`builders.total_credits`, `builders.karma`) and the rules live in code (`src/bongos/db.js`, `src/bongos/cost.js`). This doc *describes* them; where they disagree, the code wins. The two diagrams that visualize this — [task lifecycle](diagrams/01-task-lifecycle.png) and [example & karma](diagrams/03-drachmae-karma.png) — are auto-generated from the same live facts.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -173,6 +173,6 @@ Both are reputation. Neither is a spendable currency today. You don't have to *d
|
|
|
173
173
|
## See it / dig deeper
|
|
174
174
|
|
|
175
175
|
- **Your numbers:** [builders' hall](https://example.com/builders) (profile card + leaderboard), or `/builder-start` (shows your credit total).
|
|
176
|
-
- **The diagram:** [`diagrams/03-
|
|
176
|
+
- **The diagram:** [`diagrams/03-drachmae-karma.png`](diagrams/03-drachmae-karma.png) — sources and sinks at a glance.
|
|
177
177
|
- **The decisions:** [ADR 0054](../adr/0054-session-token-reward-cost-plus.md) (cost-plus reward), [ADR 0023](../adr/0023-kind-multipliers-and-idea-bonus.md) (kind multipliers + idea bonus), [ADR 0028](../adr/0028-honest-llm-spend-accounting.md) (token→USD pricing).
|
|
178
178
|
- **The code:** `src/bongos/db.js` (`REWARD_MARGIN_PCT`, `KIND_MULTIPLIERS`, `computeSessionTokenReward`), `src/bongos/llm-pricing.js` (pricing), `src/bongos/cost.js` (budget cap), `src/bongos/bfg.js` (builder strength).
|
|
@@ -10,7 +10,7 @@ When you're done, open [/builders](https://example.com/builders) and run `/build
|
|
|
10
10
|
|
|
11
11
|
## What this project is
|
|
12
12
|
|
|
13
|
-
This primer teaches the **platform** — how you claim work and ship it, the same on every Cloud Bongos instance. *What* you're building here — the product, its world, and its visual style — is specific to this instance and lives in **[`docs/project-context.md`](../project-context.md)**. Read that first for the product; everything below is the build system you'll use to make it.
|
|
13
|
+
This primer teaches the **platform** — how you claim work and ship it, the same on every Cloud Bongos instance. *What* you're building here — the product, its world, and its visual style — is specific to this instance and lives in **[`docs/project-context.template.md`](../project-context.template.md)**. Read that first for the product; everything below is the build system you'll use to make it.
|
|
14
14
|
|
|
15
15
|
## What Bongos is
|
|
16
16
|
|
package/docs/recipes/README.md
CHANGED
|
@@ -16,7 +16,7 @@ A "recipe" is not a limitation (something the project cannot do). It is a multi-
|
|
|
16
16
|
## What does not go here
|
|
17
17
|
|
|
18
18
|
- Architectural decisions → [`docs/adr/`](../../docs/adr/)
|
|
19
|
-
- Things the project cannot do →
|
|
19
|
+
- Things the project cannot do → `limitations/` (instance-side)
|
|
20
20
|
- Things stuck on the prompter's input → the blockers queue (`POST /api/gds/blockers`, reviewed via `/blocker-review`)
|
|
21
21
|
|
|
22
22
|
## Read order while working
|
|
@@ -12,11 +12,11 @@ deliberate operator step.
|
|
|
12
12
|
| Piece | Where | Role |
|
|
13
13
|
|---|---|---|
|
|
14
14
|
| Build wrapper | [`scripts/gds/build-bongos.js`](../../scripts/gds/build-bongos.js) | `bun build --compile` → per-platform binaries in `dist/` |
|
|
15
|
-
| CI |
|
|
16
|
-
| Release pointer |
|
|
15
|
+
| CI | `.github/workflows/build-bongos-cli.yml` (instance-side) | runs the wrapper, uploads `bongos-cli-binaries` artifacts (no publish) |
|
|
16
|
+
| Release pointer | `config/bongos-cli.json` (instance-side) | `releases_repo` + `version` + per-target asset filenames |
|
|
17
17
|
| Redirects | [`src/bongos-downloads.js`](../../src/bongos-downloads.js) → `/downloads/bongos/*` in [`serve-internal.js`](../../src/bongos/serve-internal.js) | `/downloads/bongos/<target>` 302s to the release asset; `/version` + `/latest.json` for update-checks |
|
|
18
|
-
| Installer |
|
|
19
|
-
| Package managers |
|
|
18
|
+
| Installer | `infra/bongos-install.sh` + `.ps1` (instance-side) | served (origin-pinned) at `/downloads/bongos/install.sh` / `.ps1` |
|
|
19
|
+
| Package managers | `packaging/homebrew/bongos.rb`, `packaging/scoop/bongos.json` (instance-side) | brew/scoop templates (fill sha256 at release) |
|
|
20
20
|
|
|
21
21
|
## What the user runs
|
|
22
22
|
|
|
@@ -62,7 +62,7 @@ node -e "console.log(require('./scripts/gds/publish-manifest').isPublishable('do
|
|
|
62
62
|
|
|
63
63
|
The ordinary claim→ship loop: `/builder-claim N` → build → `/builder-ship`. That chains `completed` → `confirmed` → `shipped`, and credits land at `shipped` ([ADR 0120](../adr/0120-pay-on-land-and-builder-owned-rebase-gate.md)).
|
|
64
64
|
|
|
65
|
-
Landing is CI-gated (tasks 1002610/1002611/1002619, ADR 0159): every PR runs the **`unit`** check — the DB-free suite plus the fitness gates, via `npm test` — and branch protection **requires** it, so a red suite blocks the merge. PRs touching the hard-floor safety surfaces (
|
|
65
|
+
Landing is CI-gated (tasks 1002610/1002611/1002619, ADR 0159): every PR runs the **`unit`** check — the DB-free suite plus the fitness gates, via `npm test` — and branch protection **requires** it, so a red suite blocks the merge. PRs touching the hard-floor safety surfaces (`CODEOWNERS` (instance-side)) additionally need the owner's review. The owner kill-switch is `node scripts/gds/gating.js on|off|status` — reversible by construction, snapshot-restored.
|
|
66
66
|
|
|
67
67
|
**Stop here and nothing happens to any user.** This is the gate that feels like the finish line and is not.
|
|
68
68
|
|
|
@@ -138,7 +138,7 @@ The mechanics of the bump itself — pre-flight, pin-verify, migrate, `.claude/`
|
|
|
138
138
|
|
|
139
139
|
Also: an instance can have **more than one plane to update** (e.g. a web instance plus a control plane that resolves its tooling from its own vendored core). Updating one does not update the other.
|
|
140
140
|
|
|
141
|
-
**Worked example — cloudbongos.com (task 1002621).** The web instance moved off `file:vendor/...` onto a bare registry pin (`bongos upgrade --to <v> --registry --force`, then commit+push `package.json`/`package-lock.json`/`.npmrc` — no tarball) and is enrolled in
|
|
141
|
+
**Worked example — cloudbongos.com (task 1002621).** The web instance moved off `file:vendor/...` onto a bare registry pin (`bongos upgrade --to <v> --registry --force`, then commit+push `package.json`/`package-lock.json`/`.npmrc` — no tarball) and is enrolled in `config/update-subscriptions.json` (instance-side) on the `patch` channel, with the `core-update-subscription` routine armed via a dedicated systemd timer on the control-plane box (daily; see [upgrading-the-core](upgrading-the-core.md) for the generic enroll/arm steps). The deploy service's `npm ci` and the routine's `npm view` both authenticate via a root-owned `NPM_TOKEN` env file wired in through each unit's `EnvironmentFile` — a read-only token never committed to any repo. The control plane's own tooling pin is a separate, not-yet-migrated plane (see above).
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
@@ -327,7 +327,7 @@ Exit codes: `0` aligned, `1` diverged, `2` undetermined.
|
|
|
327
327
|
|
|
328
328
|
| Act | Gate | Who |
|
|
329
329
|
|---|---|---|
|
|
330
|
-
| Land a change on `main` | 1 | any builder holding a claim, behind the required `unit` check (protected paths are Metic+; hard-floor paths need owner review per
|
|
330
|
+
| Land a change on `main` | 1 | any builder holding a claim, behind the required `unit` check (protected paths are Metic+; hard-floor paths need owner review per `CODEOWNERS` (instance-side); [canonical-permissions](../canonical-permissions.md)) |
|
|
331
331
|
| Bump `CORE_VERSION` | 2a | a builder, inside a claimed task |
|
|
332
332
|
| Build the artifact | 2b | any builder — `package-core` is tokenless and network-free |
|
|
333
333
|
| Publish to npm | 2c | **automatic on merge** once the owner arms the lane ([ADR 0161](../adr/0161-publish-on-merge.md)); the manual `--publish --live` fallback stays owner-only |
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Recipe — deploying a Cloud Bongos instance on a pinned core (`git reset` → `npm ci`)
|
|
2
2
|
|
|
3
|
-
> ADR [0108](../adr/0108-instance-composition-configurable-root.md) §4 (configurable-root W4, task [#1884](https://example.com/builders#/task/1884)). How a **consumer instance** — one that installs `@bongos/core` as a versioned npm dependency rather than living in the monorepo — deploys. This is the target the live droplet deploy converts to; the reference implementation is
|
|
3
|
+
> ADR [0108](../adr/0108-instance-composition-configurable-root.md) §4 (configurable-root W4, task [#1884](https://example.com/builders#/task/1884)). How a **consumer instance** — one that installs `@bongos/core` as a versioned npm dependency rather than living in the monorepo — deploys. This is the target the live droplet deploy converts to; the reference implementation is `scripts/deploy/deploy-instance.sh` (instance-side).
|
|
4
4
|
|
|
5
5
|
## The two models
|
|
6
6
|
|
|
7
7
|
| | Pre-split (today, `example`) | Post-split (consumer instance) |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| Core code | in-tree (the monorepo) | `node_modules/@bongos/core` (a pinned version) |
|
|
10
|
-
| Deploy |
|
|
10
|
+
| Deploy | `scripts/deploy/deploy-prod.sh` (instance-side): `git reset --hard origin/main` on a shared checkout | `scripts/deploy/deploy-instance.sh` (instance-side): `git pull` the instance repo → `npm ci` → migrate → restart |
|
|
11
11
|
| Upgrade the core | (n/a — one tree) | bump the pin in `package.json` → `npm ci` → migrate → restart (ADR 0108 §4; the R86 channel, task [#1690](https://example.com/builders#/task/1690)) |
|
|
12
12
|
| `cloudbongos.com` | a 2nd service on the **same** checkout (why it ran stale) | its **own** consumer repo + its own `deploy-instance.sh` |
|
|
13
13
|
|
|
@@ -23,7 +23,7 @@ Organized by topic, not chronology. New entries go in the matching section; crea
|
|
|
23
23
|
```
|
|
24
24
|
(Or set `PGHOST=/var/run/postgresql` in the environment.) That directory is the Debian/Ubuntu default Postgres socket location; on other distros it may differ.
|
|
25
25
|
- **Why it bites:** The `pg` library's defaults assume a standard TCP+password setup, which is the most common configuration but not the cheapest one for a single-machine deploy. Peer auth is more secure and frictionless once you know the socket path. The error message points at SASL, not at the host setting, which buries the real cause.
|
|
26
|
-
- **Where this lives in the codebase:** [`src/
|
|
26
|
+
- **Where this lives in the codebase:** [`src/bongos/pool.js`](../../src/bongos/pool.js) has the explicit socket host.
|
|
27
27
|
|
|
28
28
|
### Re-provisioning an EXISTING instance moves its `HOME`, orphaning any stored builder secrets
|
|
29
29
|
|
|
@@ -170,7 +170,7 @@ Organized by topic, not chronology. New entries go in the matching section; crea
|
|
|
170
170
|
|
|
171
171
|
- **Behavior (V3.R59 [#278](https://example.com/builders#/task/278)):** `.env.local` is gitignored, so `git worktree add` does not carry it over. The `SessionStart` hook [`.claude/hooks/session-start.js`](../../.claude/hooks/session-start.js) copies it from the main checkout into the worktree on first session — no manual `cp` from a sibling needed.
|
|
172
172
|
- **If the main checkout has no `.env.local`:** the hook tells you to run `/builder-setup`.
|
|
173
|
-
- **The Google AI Studio key is separate:** it resolves from `~/.config/otb/env`, which already survives worktree churn — see
|
|
173
|
+
- **The Google AI Studio key is separate:** it resolves from `~/.config/otb/env`, which already survives worktree churn — see `art/pipeline/gen_api.py` (instance-side).
|
|
174
174
|
- **All hooks run via `node`, not `bash` ([#671](https://example.com/builders#/task/671)):** `SessionStart`, `UserPromptSubmit`, and `Stop` run identically on Windows/macOS/Linux. `session-start.js` chains the env-copy + onboarding + rank-change greeting + memory pull that the old `sync-env-local.sh` shell chain ran.
|
|
175
175
|
|
|
176
176
|
---
|
|
@@ -14,7 +14,7 @@ How the `search_chunks` index (the recall layer, [ADR 0060](../adr/0060-gds-retr
|
|
|
14
14
|
|
|
15
15
|
## When it runs
|
|
16
16
|
|
|
17
|
-
A **non-blocking step in the prod deploy** (
|
|
17
|
+
A **non-blocking step in the prod deploy** (`scripts/deploy/deploy-prod.sh` (instance-side), mirrored in the droplet's hand-maintained `~/deploy.sh`), after the code update + migrations + healthz, where the new markdown and the DB are both on disk. It's wrapped `timeout 60 … || echo "…deploy unaffected"` — **an ingest failure never fails a deploy**; the next deploy (or a manual run) catches up.
|
|
18
18
|
|
|
19
19
|
> ⚠️ `scripts/deploy/deploy-prod.sh` is a **byte-faithful mirror** of the droplet's `~/deploy.sh`, which is **hand-maintained and NOT auto-installed** (the ci deploy key is forced-command-locked — [ADR 0042](../adr/0042-builder-self-deploy-ci-auto-merge.md)/[0043](../adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md)). When you change the mirror, **re-apply the same edit to `~/deploy.sh` on the droplet by hand** (`ssh lars@REDACTED_IP`), or the deploy hook won't actually run there. (Same caveat as the [prod deploy-script mirror](../../docs/recipes/ops-gotchas.md) note.)
|
|
20
20
|
|
|
@@ -46,7 +46,7 @@ Security ([ADR 0060](../adr/0060-gds-retrieval-layer.md) §6 / [ADR 0016](../adr
|
|
|
46
46
|
- **`vulnerability_reports` + `security_reports` are excluded entirely** — raw security findings don't belong in a broadly-searchable index.
|
|
47
47
|
- **Session/memory/grade text is scrubbed** through `ship.js` `scrubSecrets` (known shapes + high-entropy) before storage — the index must never hold a leaked credential.
|
|
48
48
|
|
|
49
|
-
It runs on a **nightly systemd timer** (
|
|
49
|
+
It runs on a **nightly systemd timer** (`infra/search-index-db.timer` → `infra/search-index-db.service` (instance-side), 03:30 UTC, 30 min before the pg_dump so they don't contend), modeled on `gds-db-backup`. DB prose changes more slowly than docs, so nightly is fresh enough; the repo-md half stays intra-day via the deploy hook.
|
|
50
50
|
|
|
51
51
|
**Operator install (one-time, on the droplet):**
|
|
52
52
|
|
|
@@ -89,7 +89,7 @@ The sections above are the *manual* `bongos upgrade`. The **subscription** runs
|
|
|
89
89
|
|
|
90
90
|
A **major** is never automatic; prereleases are never auto-targeted.
|
|
91
91
|
|
|
92
|
-
**Enrolling an instance** — add it to
|
|
92
|
+
**Enrolling an instance** — add it to `config/update-subscriptions.json` (instance-side) (ships empty). `env` carries per-instance vars merged into the upgrade child — set `PGDATABASE` so `migrate` hits the right DB (see the Gotchas):
|
|
93
93
|
|
|
94
94
|
```json
|
|
95
95
|
{
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/autonomy/gauge.js — the Autobongos usage gauge (task 1003901, goal
|
|
4
|
+
// 1000119, criterion wa11-unattended-ship; design of record ADR 0307 §2).
|
|
5
|
+
//
|
|
6
|
+
// WHAT THIS READS, AND WHY IT CANNOT JUST ASK. There is no `claude usage`
|
|
7
|
+
// command and nothing on disk caches remaining subscription capacity. The one
|
|
8
|
+
// structured read is the statusLine hook's input JSON, which Claude Code feeds a
|
|
9
|
+
// status script on every render:
|
|
10
|
+
//
|
|
11
|
+
// rate_limits.five_hour.{ used_percentage, resets_at }
|
|
12
|
+
// rate_limits.seven_day.{ used_percentage, resets_at } resets_at = epoch SECONDS
|
|
13
|
+
//
|
|
14
|
+
// Anthropic's own note on the field: "Only present for subscribers after first
|
|
15
|
+
// API response." So the gauge CANNOT BE READ COLD — there is no way to ask "how
|
|
16
|
+
// much is left" before a session has already started spending. The mechanism is
|
|
17
|
+
// therefore a writer (autonomy-gauge-statusline.js) that PERSISTS both windows
|
|
18
|
+
// on every render, and this reader, which the supervisor consults between tasks.
|
|
19
|
+
//
|
|
20
|
+
// THE RULE THAT MAKES IT SAFE: a gauge we cannot vouch for is UNKNOWN, never
|
|
21
|
+
// HEADROOM. A stale file, a missing file, a malformed file and a file with no
|
|
22
|
+
// rate_limits block all decide the same way — hold. Treating absence as "plenty
|
|
23
|
+
// left" is the one failure that spends a window it does not have, and it is the
|
|
24
|
+
// failure a missing file looks most like.
|
|
25
|
+
//
|
|
26
|
+
// PURE. No I/O, no clock of its own — `now` is always injected. The readers that
|
|
27
|
+
// touch disk live in the CLI (scripts/gds/autonomy-gauge.js), the same split
|
|
28
|
+
// autonomy-gate.js draws between `decide` and `gatherSignals`.
|
|
29
|
+
|
|
30
|
+
const WEEK_SECONDS = 7 * 24 * 60 * 60;
|
|
31
|
+
|
|
32
|
+
// A gauge older than this is not evidence. The five-hour window moves fast
|
|
33
|
+
// enough that a half-hour-old reading can be wrong by a lot, and the writer
|
|
34
|
+
// refreshes on every statusline render (seconds apart while a session runs), so
|
|
35
|
+
// anything this old means no session has run for a while — exactly when the
|
|
36
|
+
// number is least trustworthy.
|
|
37
|
+
const DEFAULT_STALE_AFTER_S = 1800;
|
|
38
|
+
|
|
39
|
+
// Reserve = capacity deliberately left unspent, in percentage points.
|
|
40
|
+
//
|
|
41
|
+
// It exists for exactly two reasons, and neither is thrift:
|
|
42
|
+
// 1. NOT DYING AT 99%. A worker killed mid-task by the limit is wasted work
|
|
43
|
+
// with a held claim, so we stop starting tasks while a task's worth of room
|
|
44
|
+
// remains. Until the ledger can say what a task costs, this is a constant.
|
|
45
|
+
// 2. NOT THROTTLING THE OWNER. The runner and the owner draw on the SAME
|
|
46
|
+
// five-hour window. Overnight that does not matter and the reserve drops to
|
|
47
|
+
// near zero — the whole point is to spend the idle capacity. During the
|
|
48
|
+
// owner's working hours it matters a great deal.
|
|
49
|
+
const DEFAULT_CONFIG = {
|
|
50
|
+
staleAfterS: DEFAULT_STALE_AFTER_S,
|
|
51
|
+
timezone: 'America/New_York',
|
|
52
|
+
ownerHours: { start: 8, end: 20 }, // local, [start, end)
|
|
53
|
+
reserve: { working: 25, overnight: 2 },
|
|
54
|
+
// Seven-day slack, in percentage points above an even pace. Bursting is fine;
|
|
55
|
+
// running a week's allowance down by Wednesday is not.
|
|
56
|
+
weeklySlack: 15,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function cfg(overrides = {}) {
|
|
60
|
+
return {
|
|
61
|
+
...DEFAULT_CONFIG,
|
|
62
|
+
...overrides,
|
|
63
|
+
ownerHours: { ...DEFAULT_CONFIG.ownerHours, ...(overrides.ownerHours || {}) },
|
|
64
|
+
reserve: { ...DEFAULT_CONFIG.reserve, ...(overrides.reserve || {}) },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// localHour — the owner's wall-clock hour, which is what "overnight" means.
|
|
69
|
+
// Deterministic given (now, timezone): Intl resolves the offset including DST,
|
|
70
|
+
// so this needs no zone table and cannot drift twice a year.
|
|
71
|
+
const HOUR_FORMATTERS = new Map(); // per-timezone; building one per call is pure waste
|
|
72
|
+
function hourFormatter(timezone) {
|
|
73
|
+
let f = HOUR_FORMATTERS.get(timezone);
|
|
74
|
+
if (!f) {
|
|
75
|
+
f = new Intl.DateTimeFormat('en-US', { timeZone: timezone, hour: 'numeric', hour12: false });
|
|
76
|
+
HOUR_FORMATTERS.set(timezone, f);
|
|
77
|
+
}
|
|
78
|
+
return f;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function localHour(nowEpochS, timezone) {
|
|
82
|
+
try {
|
|
83
|
+
const h = hourFormatter(timezone).format(new Date(nowEpochS * 1000));
|
|
84
|
+
const n = Number(h);
|
|
85
|
+
return Number.isFinite(n) ? (n % 24) : null;
|
|
86
|
+
} catch (_) {
|
|
87
|
+
return null; // an unknown zone must not throw; fall back to the cautious branch
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// reserveFor — how much five-hour capacity to hold back right now, and why.
|
|
92
|
+
// An unresolvable hour takes the WORKING reserve: the cautious side, because
|
|
93
|
+
// guessing "overnight" wrongly is what throttles the owner mid-afternoon.
|
|
94
|
+
function reserveFor(nowEpochS, config = {}) {
|
|
95
|
+
const c = cfg(config);
|
|
96
|
+
const hour = localHour(nowEpochS, c.timezone);
|
|
97
|
+
if (hour === null) {
|
|
98
|
+
return { pct: c.reserve.working, window: 'working', why: `timezone ${c.timezone} unresolvable — holding the working-hours reserve` };
|
|
99
|
+
}
|
|
100
|
+
const { start, end } = c.ownerHours;
|
|
101
|
+
// A window that wraps midnight (e.g. 20→8) is still one contiguous span.
|
|
102
|
+
const working = start <= end ? (hour >= start && hour < end) : (hour >= start || hour < end);
|
|
103
|
+
return working
|
|
104
|
+
? { pct: c.reserve.working, window: 'working', why: `${hour}:00 ${c.timezone} is inside the owner's hours (${start}-${end})` }
|
|
105
|
+
: { pct: c.reserve.overnight, window: 'overnight', why: `${hour}:00 ${c.timezone} is outside the owner's hours (${start}-${end}) — spend it` };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// readGauge — normalize a parsed gauge file into evidence or a named absence.
|
|
109
|
+
// Every failure shape collapses to { known: false, reason }, because they all
|
|
110
|
+
// decide identically and a caller that has to enumerate them will miss one.
|
|
111
|
+
function readGauge(raw, nowEpochS, config = {}) {
|
|
112
|
+
const c = cfg(config);
|
|
113
|
+
if (!raw || typeof raw !== 'object') return { known: false, reason: 'no gauge file — the statusline writer has never run' };
|
|
114
|
+
|
|
115
|
+
const writtenAt = Number(raw.written_at);
|
|
116
|
+
if (!Number.isFinite(writtenAt)) return { known: false, reason: 'gauge file has no readable written_at' };
|
|
117
|
+
|
|
118
|
+
const ageS = nowEpochS - writtenAt;
|
|
119
|
+
if (ageS > c.staleAfterS) {
|
|
120
|
+
return { known: false, ageS, reason: `gauge is ${Math.round(ageS / 60)}m old (stale after ${Math.round(c.staleAfterS / 60)}m) — unknown, not headroom` };
|
|
121
|
+
}
|
|
122
|
+
// A clock that ran backwards is not evidence either.
|
|
123
|
+
if (ageS < -60) return { known: false, ageS, reason: 'gauge is dated in the future — clock skew, treating as unknown' };
|
|
124
|
+
|
|
125
|
+
// STALENESS IS PER WINDOW, not per file. The writer carries a window forward
|
|
126
|
+
// when a render does not report it (so a transient absence cannot destroy the
|
|
127
|
+
// seven-day reading the pacing depends on), and a carried window keeps its own
|
|
128
|
+
// `observed_at`. Ageing the file as a whole would let that carried value ride
|
|
129
|
+
// on a fresh written_at forever — "unknown" quietly becoming "headroom".
|
|
130
|
+
// Files written before observed_at existed fall back to written_at, which is
|
|
131
|
+
// exactly what they meant.
|
|
132
|
+
const win = (w) => {
|
|
133
|
+
const o = raw[w];
|
|
134
|
+
if (!o || typeof o !== 'object') return null;
|
|
135
|
+
const used = Number(o.used_percentage);
|
|
136
|
+
const resets = Number(o.resets_at);
|
|
137
|
+
if (!Number.isFinite(used) || !Number.isFinite(resets)) return null;
|
|
138
|
+
const observedAt = Number.isFinite(Number(o.observed_at)) ? Number(o.observed_at) : writtenAt;
|
|
139
|
+
if (nowEpochS - observedAt > c.staleAfterS) return null; // this half is no longer evidence
|
|
140
|
+
return { used, resetsAt: resets, observedAt, ageS: nowEpochS - observedAt };
|
|
141
|
+
};
|
|
142
|
+
const fiveHour = win('five_hour');
|
|
143
|
+
const sevenDay = win('seven_day');
|
|
144
|
+
|
|
145
|
+
// Both windows are optional in the source ("may be absent"), but with neither
|
|
146
|
+
// there is nothing to decide on.
|
|
147
|
+
if (!fiveHour && !sevenDay) {
|
|
148
|
+
return { known: false, ageS, reason: 'gauge carries no window fresh enough to use — absent or aged out' };
|
|
149
|
+
}
|
|
150
|
+
return { known: true, ageS, fiveHour, sevenDay };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// weeklyPace — the share of the seven-day allowance an even burn would have
|
|
154
|
+
// reached by now, plus slack. Derived from resets_at rather than a stored start,
|
|
155
|
+
// so it needs no second source of truth for when the week began.
|
|
156
|
+
function weeklyPace(sevenDay, nowEpochS, config = {}) {
|
|
157
|
+
const c = cfg(config);
|
|
158
|
+
const remainingS = Math.max(0, sevenDay.resetsAt - nowEpochS);
|
|
159
|
+
const elapsedFraction = Math.min(1, Math.max(0, 1 - remainingS / WEEK_SECONDS));
|
|
160
|
+
return { elapsedFraction, allowedPct: elapsedFraction * 100 + c.weeklySlack };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// whenPaceAllows — the epoch second at which an even burn catches up with what
|
|
164
|
+
// has already been spent. The inverse of weeklyPace: this is what turns "you are
|
|
165
|
+
// ahead of pace" into a time to wake up, instead of a poll loop.
|
|
166
|
+
function whenPaceAllows(sevenDay, config = {}) {
|
|
167
|
+
const c = cfg(config);
|
|
168
|
+
const neededFraction = Math.min(1, Math.max(0, (sevenDay.used - c.weeklySlack) / 100));
|
|
169
|
+
return Math.ceil(sevenDay.resetsAt - WEEK_SECONDS * (1 - neededFraction));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// decideCapacity — the whole gauge question in one pure call:
|
|
173
|
+
// may the supervisor start another task right now, and if not, until when.
|
|
174
|
+
//
|
|
175
|
+
// Order is deliberate. Unknown first (we cannot reason at all). Then the
|
|
176
|
+
// seven-day pace, because the week is the binding constraint on a heavy run and
|
|
177
|
+
// a five-hour window that looks fine can still be the one that burns the week.
|
|
178
|
+
// Then the five-hour reserve.
|
|
179
|
+
function decideCapacity({ gauge, now, config = {} } = {}) {
|
|
180
|
+
const c = cfg(config);
|
|
181
|
+
const g = readGauge(gauge, now, c);
|
|
182
|
+
|
|
183
|
+
if (!g.known) return { go: false, unknown: true, sleepUntil: null, reason: g.reason };
|
|
184
|
+
|
|
185
|
+
if (g.sevenDay) {
|
|
186
|
+
const pace = weeklyPace(g.sevenDay, now, c);
|
|
187
|
+
if (g.sevenDay.used >= 100) {
|
|
188
|
+
return { go: false, unknown: false, sleepUntil: g.sevenDay.resetsAt, window: 'seven_day',
|
|
189
|
+
reason: `seven-day window spent (${g.sevenDay.used.toFixed(1)}%) — sleeping to its reset` };
|
|
190
|
+
}
|
|
191
|
+
if (g.sevenDay.used > pace.allowedPct) {
|
|
192
|
+
const until = whenPaceAllows(g.sevenDay, c);
|
|
193
|
+
// Never propose a wake in the past, and never past the reset itself.
|
|
194
|
+
const sleepUntil = Math.min(Math.max(until, now + 1), g.sevenDay.resetsAt);
|
|
195
|
+
return { go: false, unknown: false, sleepUntil, window: 'seven_day',
|
|
196
|
+
reason: `seven-day burn ${g.sevenDay.used.toFixed(1)}% is ahead of pace (${pace.allowedPct.toFixed(1)}% allowed at ${(pace.elapsedFraction * 100).toFixed(0)}% through the week) — holding so the week does not go dark` };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (g.fiveHour) {
|
|
201
|
+
const reserve = reserveFor(now, c);
|
|
202
|
+
const ceiling = 100 - reserve.pct;
|
|
203
|
+
if (g.fiveHour.used >= ceiling) {
|
|
204
|
+
return { go: false, unknown: false, sleepUntil: g.fiveHour.resetsAt, window: 'five_hour',
|
|
205
|
+
reason: `five-hour burn ${g.fiveHour.used.toFixed(1)}% is at or past the ${ceiling}% ceiling (${reserve.window} reserve ${reserve.pct}%: ${reserve.why}) — sleeping to its reset` };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const bits = [];
|
|
210
|
+
if (g.fiveHour) bits.push(`5h ${g.fiveHour.used.toFixed(1)}%`);
|
|
211
|
+
if (g.sevenDay) bits.push(`7d ${g.sevenDay.used.toFixed(1)}%`);
|
|
212
|
+
return { go: true, unknown: false, sleepUntil: null, reason: `headroom (${bits.join(', ')}; gauge ${Math.round(g.ageS)}s old)` };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
decideCapacity, readGauge, reserveFor, weeklyPace, whenPaceAllows, localHour,
|
|
217
|
+
cfg, DEFAULT_CONFIG, WEEK_SECONDS, DEFAULT_STALE_AFTER_S,
|
|
218
|
+
};
|
|
@@ -35,7 +35,7 @@ What it does beyond the switch — each fixing a failure observed in the
|
|
|
35
35
|
and, when the published release is strictly newer, shows the Update banner; the
|
|
36
36
|
Update button downloads the installer and hands it to the OS (browser-download
|
|
37
37
|
fallback on any error). **Bumping `config/devbox-app.json` version is what makes
|
|
38
|
-
the banner appear in the field** — see [`docs/recipes/devbox-app-release.md`](
|
|
38
|
+
the banner appear in the field** — see [`docs/recipes/devbox-app-release.md`](../../../docs/recipes/devbox-app-release.md).
|
|
39
39
|
- `renderer/` — the panel UI (sandboxed, contextIsolated; preload exposes
|
|
40
40
|
named verbs only).
|
|
41
41
|
- `scripts/gen-icons.js` — regenerates all icons from pixel grids (`npm run
|
|
@@ -323,6 +323,10 @@ function createClient(opts = {}) {
|
|
|
323
323
|
// GET /copy-desk/strings — rank: any-builder — GET /copy-desk/strings
|
|
324
324
|
getCopyDeskStrings: (args) => request("GET", "/copy-desk/strings", { hasBody: false }, args),
|
|
325
325
|
},
|
|
326
|
+
"coreUpdate": {
|
|
327
|
+
// GET /core-update — rank: archon — GET /core-update
|
|
328
|
+
getCoreUpdate: (args) => request("GET", "/core-update", { hasBody: false }, args),
|
|
329
|
+
},
|
|
326
330
|
"cost": {
|
|
327
331
|
// POST /cost — rank: any-builder — POST /cost
|
|
328
332
|
postCost: (args) => request("POST", "/cost", { hasBody: true }, args),
|