@bongos/core 1.19.1074 → 1.19.1076
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 +196 -211
- package/.claude/skills/blocker-solve/SKILL.md +1 -1
- package/README.md +1 -1
- package/bin/bongos.js +1 -3
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +1 -1
- package/docs/adr/0035-builder-onboarding-three-paths.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0045-devbox-desktop-app.md +1 -1
- package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
- package/docs/adr/0053-scoped-dev-box-session.md +1 -1
- package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
- package/docs/adr/0057-container-cost-ledger.md +1 -1
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +1 -1
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +1 -1
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +1 -1
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +1 -1
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +1 -1
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +1 -1
- package/docs/adr/0145-devbox-app-branding-driven-module.md +1 -1
- package/docs/adr/0148-task-scoped-box-source-access.md +2 -2
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +1 -1
- package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
- package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
- package/docs/adr/0346-dev-boxes-are-retired.md +69 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +4 -4
- package/docs/architecture.md +5 -6
- package/docs/branding-contract.md +2 -2
- package/docs/canonical-permissions.md +10 -18
- package/docs/copy-inventory.md +3 -3
- package/docs/copy-registry.json +3 -3
- package/docs/design/gate-navigation-direction.md +1 -1
- package/docs/design/hall-direction-v2.md +3 -3
- package/docs/design/modular-architecture/00-research-report.md +1 -1
- package/docs/design/modular-architecture/01-architecture.md +3 -3
- package/docs/design/modular-architecture/02-module-map.md +1 -2
- package/docs/design/modular-architecture/03-builder-flows.md +22 -20
- package/docs/design/modular-architecture/04-module-lifecycle.md +1 -1
- package/docs/design/modular-architecture/README.md +4 -2
- package/docs/design/reviews/hall-v2/README.md +2 -2
- package/docs/design/vanilla-hall-ui-redesign-scope.md +4 -4
- package/docs/file-map.md +5 -5
- package/docs/handoff-template.md +1 -1
- package/docs/module-api-changelog.md +8 -0
- package/docs/modules-contract.md +11 -14
- package/docs/page-readings.json +63 -63
- package/docs/recipes/bongos-cli-release.md +1 -2
- package/docs/recipes/multi-builder-merge.md +1 -1
- package/docs/recipes/ops-gotchas.md +0 -9
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/windows-builders.md +1 -1
- package/migrations/core_258_drop_dev_box_tables.sql +3 -0
- package/modules/copy-desk/routes/copy-desk.js +1 -1
- package/modules/discord/ship-broadcast.js +3 -3
- package/modules/economy/credits.js +2 -2
- package/modules/hall-ui/public/palette.js +143 -3
- package/modules/ideas/projection.js +1 -1
- package/modules/lifecycle/db-ship.js +4 -4
- package/modules/lifecycle/github-push.js +12 -14
- package/modules/lifecycle/publish-reconciler.js +10 -10
- package/modules/lifecycle/routes/tasks.js +8 -8
- package/modules/lifecycle/ship-card.js +1 -1
- package/modules/lifecycle/task-visuals.js +2 -2
- package/modules/memory/routes/memory.js +2 -2
- package/modules/platform-identity/routes/sso.js +3 -2
- package/modules/provisioning/capacity.js +1 -1
- package/modules/provisioning/provisioning.js +4 -3
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/sessions/db.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +28 -0
- package/scripts/gds/artifact-staleness.js +2 -2
- package/scripts/gds/backfill-task-rewards.js +5 -5
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +7 -7
- package/scripts/gds/codemod-rename-src-gds.js +1 -1
- package/scripts/gds/context-pack.js +5 -7
- package/scripts/gds/dev-box-guard.js +165 -0
- package/scripts/gds/doctor.js +3 -3
- package/scripts/gds/fitness-checks-packaging.js +4 -4
- package/scripts/gds/fitness-lib.js +10 -0
- package/scripts/gds/fitness-ratchets.js +1 -1
- package/scripts/gds/fitness.js +2 -10
- package/scripts/gds/gen-api-client.js +4 -4
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/gen-repo-map.js +13 -14
- package/scripts/gds/gen-session-index.js +8 -8
- package/scripts/gds/leak-scan-allowlist.js +1 -1
- package/scripts/gds/plain-cards.js +1 -1
- package/scripts/gds/push-path-brief.js +10 -10
- package/scripts/gds/rename-history-check.js +1 -1
- package/scripts/gds/run-unit-tests.js +4 -0
- package/scripts/gds/session-digest-build.js +2 -2
- package/scripts/gds/ship-deploy-target.js +13 -13
- package/scripts/gds/ship-flow.js +1 -1
- package/scripts/gds/ship-land.js +6 -6
- package/scripts/gds/ship-merge.js +4 -4
- package/scripts/gds/ship-preflight-steps.js +4 -4
- package/scripts/gds/ship-regen.js +9 -12
- package/scripts/gds/ship.js +9 -9
- package/scripts/gds/skill-preflight.js +9 -8
- package/src/bongos/api-prefix.js +4 -4
- package/src/bongos/module-scope-map.js +1 -1
- package/src/bongos/platform-visibility-gate.js +1 -1
- package/src/bongos/routes/me.js +1 -1
- package/src/bongos/routes/security.js +3 -3
- package/src/bongos/routes.js +2 -2
- package/src/bongos/serve-internal.js +5 -6
- package/src/bongos-downloads.js +1 -6
- package/src/branding.js +2 -2
- package/src/build-info.js +4 -6
- package/src/instance-config.js +2 -2
- package/src/module-api.js +3 -3
- package/src/module-loader/manifest-schema.js +1 -1
- package/tests/api_alias_caller_ratchet.mjs +1 -1
- package/tests/api_alias_exceptions.mjs +1 -1
- package/tests/api_client.mjs +1 -1
- package/tests/api_path_404.mjs +1 -1
- package/tests/backfill_task_rewards.mjs +1 -1
- package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
- package/tests/cli_sessions.mjs +1 -1
- package/tests/context_pack.mjs +13 -13
- package/tests/copy_inventory.mjs +7 -7
- package/tests/core_258_box_session_claims_db.mjs +180 -0
- package/tests/credit_grant.mjs +2 -2
- package/tests/criterion_suggest.mjs +1 -1
- package/tests/design_tokens_sync.mjs +3 -2
- package/tests/fitness.mjs +89 -0
- package/tests/fitness_ratchets.mjs +2 -2
- package/tests/github_push_land_proof.mjs +1 -1
- package/tests/goal_advisory.mjs +3 -3
- package/tests/goal_suggest.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +4 -3
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_palette.mjs +224 -11
- package/tests/helpers.mjs +1 -1
- package/tests/host_topology_skips.mjs +2 -2
- package/tests/migration_namespace.mjs +6 -6
- package/tests/module_api.mjs +2 -2
- package/tests/module_cli.mjs +5 -5
- package/tests/module_manifest.mjs +13 -13
- package/tests/module_route_rank.mjs +2 -2
- package/tests/modules_route_wiring.mjs +1 -1
- package/tests/onboarding_route_signals.mjs +1 -1
- package/tests/paste_token_session_store.mjs +1 -1
- package/tests/platform_visibility_gate.mjs +1 -1
- package/tests/project_modules_ui.mjs +17 -17
- package/tests/projects_hub_module_picker.mjs +75 -74
- package/tests/provisioning_capacity.mjs +1 -1
- package/tests/provisioning_recommendations.mjs +3 -2
- package/tests/publish_branch_route.mjs +1 -1
- package/tests/publish_manifest.mjs +1 -1
- package/tests/publish_reconciler.mjs +8 -8
- package/tests/push_path_brief.mjs +10 -9
- package/tests/regrade_eligibility.mjs +1 -1
- package/tests/rename_history_restraint.mjs +1 -1
- package/tests/repo_map.mjs +9 -9
- package/tests/seam_wiring_guard.mjs +11 -11
- package/tests/search_isolation.mjs +3 -3
- package/tests/session_records.mjs +1 -1
- package/tests/session_rename_fallback.mjs +1 -1
- package/tests/session_start_freshness.mjs +3 -3
- package/tests/session_token_hash_db.mjs +1 -1
- package/tests/ship_card.mjs +1 -1
- package/tests/ship_ci_deploy.mjs +17 -17
- package/tests/ship_premerge.mjs +1 -1
- package/tests/ship_resume.mjs +1 -1
- package/tests/skill_preflight.mjs +2 -2
- package/tests/skip_is_not_pass.mjs +1 -1
- package/tests/terms_acceptance.mjs +1 -1
- package/docs/design/reviews/hall-v2/harbor--archon.webp +0 -0
- package/docs/design/reviews/hall-v2/harbor--metic.webp +0 -0
- package/docs/design/reviews/hall-v2/harbor--xenos.webp +0 -0
- package/docs/design/reviews/hall-v2/pair--archon.webp +0 -0
- package/docs/design/reviews/hall-v2/pair--metic.webp +0 -0
- package/docs/design/reviews/hall-v2/pair--xenos.webp +0 -0
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
How the compiled `bongos` binaries get built, published, and installed — the
|
|
4
4
|
"no Node, no repo" on-ramp (task [#1248](https://example.com/builders#/task/1248), V4.R81).
|
|
5
|
-
|
|
6
|
-
**CI builds, the operator publishes to a public releases repo, the server serves
|
|
5
|
+
The release flow: **CI builds, the operator publishes to a public releases repo, the server serves
|
|
7
6
|
branded redirects.** There is no cross-repo write token in CI — publishing is a
|
|
8
7
|
deliberate operator step.
|
|
9
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Multi-builder merge safety — audit + serialization model
|
|
2
2
|
|
|
3
|
-
> **Deploy-mode note (current state).** This recipe documents the **`laptop`** merge path (operator merges `main` + SSHes the droplet). Since the 2026-06-13 cutover `config/deploy.json` is **`mode: ci`** — the merge + deploy run **server-side** (PR auto-merge + `.github/workflows/deploy-prod.yml`), and
|
|
3
|
+
> **Deploy-mode note (current state).** This recipe documents the **`laptop`** merge path (operator merges `main` + SSHes the droplet). Since the 2026-06-13 cutover `config/deploy.json` is **`mode: ci`** — the merge + deploy run **server-side** (PR auto-merge + `.github/workflows/deploy-prod.yml`), and a checkout with no GitHub push credential ships via server-mediated publish ([ADR 0055](../adr/0055-server-mediated-branch-publish.md)). The local-`main`-worktree + merge-lock model below applies only if `deploy.json` is reverted to `mode: laptop`.
|
|
4
4
|
|
|
5
5
|
> **Task:** V3.R50 / [#269](https://example.com/builders#/task/269) (criterion **C6 — parallel-safe-under-load**).
|
|
6
6
|
> **Question it answers:** when N>1 builders ship and merge concurrently, where do `ship.js` and `/merge-mode` assume "I am the only builder," and what serializes the shared state so they don't collide?
|
|
@@ -282,15 +282,6 @@ Inside the namespace the file lists as `---------- 1 root root 0`. The runner un
|
|
|
282
282
|
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
-
## Dev box
|
|
286
|
-
|
|
287
|
-
### `bypassPermissions` "still not available" even though the box was provisioned for it (task 2022)
|
|
288
|
-
|
|
289
|
-
- **Symptom:** you reconnect to the browser terminal (or SSH) on a dev box and `claude` still stops for permission prompts, even though tasks [#1251](https://example.com/builders#/task/1251) and [#1314](https://example.com/builders#/task/1314) default every box to `bypassPermissions`.
|
|
290
|
-
- **Root cause:** `otb-box-launch.sh` (behind `box-terminal.service`) only ever runs `claude` once — the first time it creates the tmux `otb` session. Every later connect just reattaches to that **same running process**. The `defaultMode=bypassPermissions` setting is written into `~/.claude/settings.json` by `box-source-fetch.sh`'s cron, on its own `*/10` schedule (and, on an already-provisioned box, only after that box's baked copy of the script has self-updated to even contain the fix). If the `otb` tmux session got created before that write landed — an older box, or a fresh box whose very first connect raced the first cron tick — `claude` started once in normal `auto` mode and is stuck there for the life of the box; reconnecting never re-launches it, so the fix never takes effect even once the setting is correct on disk. (Flagged as an open follow-up in [ADR 0104](../adr/0104-trust-gds-api-channel-in-auto-mode.md).)
|
|
291
|
-
- **Fix:** `otb-box-launch.sh` now asserts `defaultMode=bypassPermissions` itself, synchronously, right before it creates a **new** `otb` tmux session — so a fresh session never depends on cron timing. This prevents the race going forward but does **not** repair a box whose `otb` session is already running in the wrong mode: for that, from an SSH/browser-terminal shell run `tmux kill-session -t otb` then reconnect (the launcher creates a fresh session and picks up the corrected setting immediately). Killing the session only drops the terminal pane — it does not touch `/workspace` or in-flight git state.
|
|
292
|
-
- **Why it bites:** every layer *looks* fixed — `~/.claude/settings.json` has the right value, `IS_SANDBOX=1` is set — because you're inspecting the box's filesystem, not the already-running process that never re-read it.
|
|
293
|
-
|
|
294
285
|
## New-project scaffolds & the core pin (gap 13 · task 1002706 · ADR 0168)
|
|
295
286
|
|
|
296
287
|
### Do NOT set `PROVISION_CORE_FROM_REGISTRY=1` on the box until the core package goes public
|
|
@@ -12,7 +12,7 @@ docker compose up
|
|
|
12
12
|
|
|
13
13
|
That builds the image, starts Postgres, applies the build-system migrations (game-module migrations are skipped — this is a Bongos-only instance), and serves the builders' hall at **http://localhost:3000**.
|
|
14
14
|
|
|
15
|
-
- The instance is **vanilla** by default: the neutral Cloud Bongos brand, every feature module (game, art pipeline, Discord
|
|
15
|
+
- The instance is **vanilla** by default: the neutral Cloud Bongos brand, every feature module (game, art pipeline, Discord) **off**. `.dockerignore` keeps the host's `config/branding.json` and `config/modules.json` out of the image, so it resolves to `config/branding.neutral.json` + `config/modules.neutral.json`.
|
|
16
16
|
- **No OTB scope-truth is seeded.** OTB/product data (game versions, goals, done-when criteria) lives in opt-in seed migrations under `migrations/instance/` (plus a few guarded blocks in the mixed files 003/006). `scripts/migrate.sh` skips them unless `GDS_APPLY_INSTANCE_SEEDS=1`, so a fresh DB comes up with only the schema and platform-universal defaults (ranks, achievements) — no crash, no foreign-key failures, no leftover versions. Platform system principals (`bfg-system`, `discord-bot-system`) are generic and remain. See task 1704 / [ADR 0105](../adr/0105-instance-seed-migrations-out-of-core.md).
|
|
17
17
|
- **No prod topology** is referenced: the database is a local container, the credentials are local demo values, and the app reaches Postgres over TCP via `PG*` env (see `docker-compose.yml`).
|
|
18
18
|
|
|
@@ -227,7 +227,7 @@ here, suspect these before assuming the platform is at fault:
|
|
|
227
227
|
is a short path (`C:\Users\RUNNER~1\…`), `os.tmpdir()` keeps the short form while git and the
|
|
228
228
|
shell answer with the long one — so two spellings of one directory compare unequal. This alone
|
|
229
229
|
explained failures in `ship_preflight`, `migrate_root_resolution` and `sandbox_stage`, and the
|
|
230
|
-
last of those was a genuine bug: the dev box would have git-synced a checkout onto itself.
|
|
230
|
+
last of those was a genuine bug: the since-retired dev box ([ADR 0346](../adr/0346-dev-boxes-are-retired.md)) would have git-synced a checkout onto itself.
|
|
231
231
|
- **A REPO-relative path must be POSIX on every platform** — it goes into a task's `touches[]`, a
|
|
232
232
|
version row, or a generated manifest. `path.join`/`path.relative` emit `\` here. Use
|
|
233
233
|
`path.posix.*`, or the house spelling `.split(path.sep).join('/')`. This was the bug in
|
|
@@ -46,6 +46,9 @@ DROP TABLE IF EXISTS builder_boxes;
|
|
|
46
46
|
DROP TABLE IF EXISTS box_intents;
|
|
47
47
|
DROP TABLE IF EXISTS builder_ssh_keys;
|
|
48
48
|
|
|
49
|
+
-- claims.creator_session_id REFERENCES builder_sessions with no ON DELETE, so unlink box sessions first (task 1004362).
|
|
50
|
+
UPDATE claims SET creator_session_id = NULL
|
|
51
|
+
WHERE creator_session_id IN (SELECT id FROM builder_sessions WHERE source = 'box');
|
|
49
52
|
DELETE FROM builder_sessions WHERE source = 'box';
|
|
50
53
|
ALTER TABLE builder_sessions
|
|
51
54
|
DROP CONSTRAINT IF EXISTS builder_sessions_source_check;
|
|
@@ -66,7 +66,7 @@ const { waitingRoundOrFail, refuseDecision, nextWaiting } = require('../page-dec
|
|
|
66
66
|
const { validateOrRespond } = api;
|
|
67
67
|
|
|
68
68
|
// The doorway's namespaced logger, not console.error (task 1003114). Same seam
|
|
69
|
-
// builder-settings /
|
|
69
|
+
// builder-settings / ideas / government already use: a module's log
|
|
70
70
|
// lines come out structured and attributable, and an error object logged as
|
|
71
71
|
// `{ err }` keeps its stack instead of being stringified into one line.
|
|
72
72
|
const log = api.logger('copy-desk');
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
// either path. ship.js re-exports it so its existing regression test is unchanged.
|
|
9
9
|
//
|
|
10
10
|
// postShipBroadcast is the SERVER-SIDE poster (task 1045). On the server-mediated
|
|
11
|
-
// publish path
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// publish path the uploading checkout cannot post the broadcast — it holds no
|
|
12
|
+
// webhook secret, which is exactly why such a ship was silently skipping the
|
|
13
|
+
// public announcement. So when the GDS server confirms the deploy
|
|
14
14
|
// landed (publish-status reconcile, confirmed→shipped), the server — which holds
|
|
15
15
|
// the webhook secrets and just deployed — fires the broadcast instead. Best-effort:
|
|
16
16
|
// it never throws, so a Discord hiccup can never undo a ship.
|
|
@@ -919,7 +919,7 @@ async function awardIdeaGovernancePassCredit(client, {
|
|
|
919
919
|
// column the route's empty-session guard checks). Used ONLY as a
|
|
920
920
|
// fallback: when modelUsage prices to $0 but the session carried
|
|
921
921
|
// real tokens, the reward is priced from totalTokens at the opus
|
|
922
|
-
// rate so the
|
|
922
|
+
// rate so the CI agent path (which historically uploaded
|
|
923
923
|
// empty/unpriceable model_usage → $0 reward; ADR 0097) still pays
|
|
924
924
|
// the real cost-plus amount. This is NOT a fabricated dollar value
|
|
925
925
|
// — it prices REAL token counts via the same locked PRICES table.
|
|
@@ -941,7 +941,7 @@ async function awardIdeaGovernancePassCredit(client, {
|
|
|
941
941
|
// what actually paid.
|
|
942
942
|
//
|
|
943
943
|
// Cost-plus zero-out fallback (task 1676). priceModelUsage returns $0 when
|
|
944
|
-
// model_usage is empty or carries no token counts — the exact
|
|
944
|
+
// model_usage is empty or carries no token counts — the exact CI-agent symptom
|
|
945
945
|
// ADR 0097 records ("upload no priced model usage"). When that happens but the
|
|
946
946
|
// session DID carry real tokens, price the total at the opus rate so the reward
|
|
947
947
|
// reflects the real spend instead of silently rounding to 0. The blend assumes a
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Progressive enhancement is the contract: a page that does not load palette.js
|
|
10
10
|
// keeps a hidden slot and no key handler, and nothing in shell.js calls in here.
|
|
11
11
|
//
|
|
12
|
-
// The index
|
|
12
|
+
// The index has three CLIENT-SIDE layers (task 2178 scope):
|
|
13
13
|
// 1. Pages — read live from the sidebar shell.js rendered, so rank + module
|
|
14
14
|
// gating come for free and can never drift from the nav. A hidden nav item
|
|
15
15
|
// is a hidden destination. (Cosmetic only; the server still gates each page.)
|
|
@@ -17,12 +17,33 @@
|
|
|
17
17
|
// cannot be read from another page's DOM, so they are declared below and
|
|
18
18
|
// held to the real ids by tests/hall_palette.mjs.
|
|
19
19
|
// 3. Records — id syntax: `1234`, `g 12`, `b 7`, `i 42`.
|
|
20
|
-
//
|
|
20
|
+
// PLUS one server-backed layer (task 1003460): typing prose (3+ chars, debounced,
|
|
21
|
+
// per-open memoized) asks POST /api/bongos/search { source_kind: 'db' } — the
|
|
22
|
+
// rank/owner-scoped recall layer (ADR 0060) that already indexes every
|
|
23
|
+
// task/idea/blocker/doc, and had zero hall consumers before this. Content hits
|
|
24
|
+
// are merged in ON TOP of the synchronous local compute() — never replacing it —
|
|
25
|
+
// which is what keeps this progressive enhancement: a slow, failed, or killed
|
|
26
|
+
// endpoint just means the merge never happens, and nav-jump/id-resolution/
|
|
27
|
+
// page-jump (all pure, all local) are completely unaffected. Only source_refs
|
|
28
|
+
// this palette can actually ROUTE to are surfaced (task/idea/blocker today — see
|
|
29
|
+
// contentEntry) — a doc/session-log/commit hit is real prose the endpoint found,
|
|
30
|
+
// but the hall has no page to jump to for it, and a palette row that goes
|
|
31
|
+
// nowhere on Enter is worse than not showing it.
|
|
21
32
|
(function () {
|
|
22
33
|
'use strict';
|
|
23
34
|
|
|
24
35
|
var PALETTE_ID = 'jump-palette';
|
|
25
36
|
var MAX_RESULTS = 40;
|
|
37
|
+
var SEARCH_MIN_LEN = 3; // 1-2 chars is websearch_to_tsquery's least selective case (falls through
|
|
38
|
+
// to the unindexed trigram leg AND a zero-result corpusHealth() probe) —
|
|
39
|
+
// not worth a round trip on the palette's most common input length.
|
|
40
|
+
var SEARCH_DEBOUNCE_MS = 300; // paced against searchRateLimit (60/min/builder, modules/memory/routes/
|
|
41
|
+
// search.js) — a builder typing a phrase word-by-word still fires a
|
|
42
|
+
// handful of requests, not one per keystroke.
|
|
43
|
+
var SEARCH_LIMIT = 8;
|
|
44
|
+
var SEARCH_TITLE_MAX = 120; // an indexed title is write-once ledger data — cap it before it's markup.
|
|
45
|
+
var SEARCH_CACHE_MAX = 30; // per-open memo (cleared on open()) so retyping a prefix replays the
|
|
46
|
+
// last response instead of re-issuing an identical search.
|
|
26
47
|
|
|
27
48
|
// Sub-destinations, grouped by the nav item that owns them. `gate` names the
|
|
28
49
|
// element whose visibility decides whether the group is offered — the nav item
|
|
@@ -79,6 +100,10 @@
|
|
|
79
100
|
var results = [];
|
|
80
101
|
var cursor = 0;
|
|
81
102
|
var lastFocus = null;
|
|
103
|
+
var searchSeq = 0; // bumped on every keystroke/close — invalidates in-flight requests
|
|
104
|
+
var searchTimer = null;
|
|
105
|
+
var searchAbort = null; // the in-flight request's AbortController, if the runtime has one
|
|
106
|
+
var searchCache = {}; // query (trimmed+lowercased) -> raw hits[], this OPEN only
|
|
82
107
|
|
|
83
108
|
function esc(s) {
|
|
84
109
|
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
@@ -153,6 +178,116 @@
|
|
|
153
178
|
return null;
|
|
154
179
|
}
|
|
155
180
|
|
|
181
|
+
// V4 (docs/voice.md): a dash is banned in copy this project QUOTES, not copy it
|
|
182
|
+
// authors, enforced by normalising at render — never by editing the ledger. A
|
|
183
|
+
// task/idea/blocker title is exactly that quoted case. Same normalisation as
|
|
184
|
+
// modules/public-landing/public/index.html's cleanTitle() / goal-inbox.js's.
|
|
185
|
+
function cleanTitle(raw) {
|
|
186
|
+
return String(raw == null ? '' : raw)
|
|
187
|
+
.replace(/\s*[—–]\s*/g, ', ')
|
|
188
|
+
.replace(/,\s*,/g, ',')
|
|
189
|
+
.replace(/\s*\([^()]*\)\s*$/, '')
|
|
190
|
+
.replace(/[\s,]+$/, '')
|
|
191
|
+
.trim();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function truncate(s, max) {
|
|
195
|
+
s = String(s == null ? '' : s);
|
|
196
|
+
return s.length > max ? s.slice(0, max - 1).trim() + '…' : s;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Map one POST /search hit to a palette entry, or null when this palette has
|
|
200
|
+
// nowhere to send the reader for that kind of hit (see the header note). Only
|
|
201
|
+
// source_kind='db' rows carry a source_ref this palette recognizes today —
|
|
202
|
+
// task/idea/blocker, the same three id-syntax already resolves in recordEntry.
|
|
203
|
+
// Scoping (rank + owner) is entirely the endpoint's (ADR 0016/0060) — this is
|
|
204
|
+
// presentation only, never a second filter.
|
|
205
|
+
function contentEntry(hit) {
|
|
206
|
+
if (!hit || hit.source_kind !== 'db') return null;
|
|
207
|
+
var ref = String(hit.source_ref || '');
|
|
208
|
+
var label = function (fallback) { return truncate(cleanTitle(hit.title) || fallback, SEARCH_TITLE_MAX); };
|
|
209
|
+
var m = ref.match(/^task:(\d+)$/);
|
|
210
|
+
if (m) return { label: label('Task ' + m[1]), group: 'Content', href: '/task/' + m[1] };
|
|
211
|
+
m = ref.match(/^idea:(\d+)$/);
|
|
212
|
+
if (m) return { label: label('Idea ' + m[1]), group: 'Content', href: '#/idea/' + m[1], overlay: true };
|
|
213
|
+
m = ref.match(/^blocker:(\d+)$/);
|
|
214
|
+
if (m) return { label: label('Blocker ' + m[1]), group: 'Content', href: '#/blocker/' + m[1], overlay: true };
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Merge raw /search hits into the rendered results, deduped by destination
|
|
219
|
+
// href against whatever's already showing (a local id-syntax match resolving
|
|
220
|
+
// the same task, or an earlier merge). MAX_RESULTS is the same ceiling the
|
|
221
|
+
// local pool is held to.
|
|
222
|
+
function mergeHits(hits) {
|
|
223
|
+
var seen = {};
|
|
224
|
+
results.forEach(function (e) { seen[e.href] = true; });
|
|
225
|
+
var added = [];
|
|
226
|
+
hits.forEach(function (hit) {
|
|
227
|
+
var e = contentEntry(hit);
|
|
228
|
+
if (e && !seen[e.href]) { seen[e.href] = true; added.push(e); }
|
|
229
|
+
});
|
|
230
|
+
if (!added.length) return;
|
|
231
|
+
results = results.concat(added).slice(0, MAX_RESULTS);
|
|
232
|
+
render();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Debounced remote leg. Fires at most once per SEARCH_DEBOUNCE_MS of typing,
|
|
236
|
+
// and every keystroke invalidates whatever was scheduled/in-flight before it
|
|
237
|
+
// (searchSeq, plus actually aborting the request in flight so a superseded
|
|
238
|
+
// query doesn't keep FTS/trgm work running server-side) so a slow response
|
|
239
|
+
// for a stale query can never land after a newer one, or after the palette
|
|
240
|
+
// closed. A failed fetch (network error, a non-2xx, an abort, or `fetch` just
|
|
241
|
+
// not existing) resolves to nothing being appended — the synchronous local
|
|
242
|
+
// compute() already rendered, so the palette degrades to exactly today's
|
|
243
|
+
// behaviour, never an error state.
|
|
244
|
+
function scheduleSearch(query) {
|
|
245
|
+
if (searchTimer) { clearTimeout(searchTimer); searchTimer = null; }
|
|
246
|
+
if (searchAbort) { searchAbort.abort(); searchAbort = null; }
|
|
247
|
+
searchSeq++;
|
|
248
|
+
var mySeq = searchSeq;
|
|
249
|
+
var q = String(query || '').trim();
|
|
250
|
+
var key = q.toLowerCase();
|
|
251
|
+
if (q.length < SEARCH_MIN_LEN) return;
|
|
252
|
+
if (Object.prototype.hasOwnProperty.call(searchCache, key)) { mergeHits(searchCache[key]); return; }
|
|
253
|
+
if (typeof fetch !== 'function') return;
|
|
254
|
+
searchTimer = setTimeout(function () { runSearch(q, key, mySeq); }, SEARCH_DEBOUNCE_MS);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function cacheHits(key, hits) {
|
|
258
|
+
// Unbounded within one open() would leak for a palette left open a long
|
|
259
|
+
// time; this is a per-open convenience, not a durable cache, so just drop
|
|
260
|
+
// the oldest entry rather than a real LRU.
|
|
261
|
+
var keys = Object.keys(searchCache);
|
|
262
|
+
if (keys.length >= SEARCH_CACHE_MAX) delete searchCache[keys[0]];
|
|
263
|
+
searchCache[key] = hits;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function runSearch(q, key, mySeq) {
|
|
267
|
+
var controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
268
|
+
searchAbort = controller;
|
|
269
|
+
fetch('/api/bongos/search', {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
credentials: 'same-origin',
|
|
272
|
+
headers: { 'Content-Type': 'application/json' },
|
|
273
|
+
body: JSON.stringify({ q: q, limit: SEARCH_LIMIT, source_kind: 'db' }),
|
|
274
|
+
signal: controller ? controller.signal : undefined,
|
|
275
|
+
}).then(function (r) {
|
|
276
|
+
return r && r.ok ? r.json() : null;
|
|
277
|
+
}).then(function (data) {
|
|
278
|
+
if (searchAbort === controller) searchAbort = null;
|
|
279
|
+
// Stale (a newer keystroke/search ran) or the palette closed meanwhile.
|
|
280
|
+
if (mySeq !== searchSeq || !isOpen()) return;
|
|
281
|
+
if (!data || !Array.isArray(data.results)) return;
|
|
282
|
+
cacheHits(key, data.results);
|
|
283
|
+
mergeHits(data.results);
|
|
284
|
+
}).catch(function () {
|
|
285
|
+
if (searchAbort === controller) searchAbort = null;
|
|
286
|
+
// Fail-open: killing the endpoint (or aborting a superseded request)
|
|
287
|
+
// leaves the palette exactly as useful as today.
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
156
291
|
function compute(q) {
|
|
157
292
|
var query = String(q || '').trim().toLowerCase();
|
|
158
293
|
var rec = recordEntry(query);
|
|
@@ -227,6 +362,7 @@
|
|
|
227
362
|
results = compute(input.value);
|
|
228
363
|
cursor = 0;
|
|
229
364
|
render();
|
|
365
|
+
scheduleSearch(input.value);
|
|
230
366
|
});
|
|
231
367
|
listEl.addEventListener('click', function (ev) {
|
|
232
368
|
var li = ev.target.closest ? ev.target.closest('[data-i]') : null;
|
|
@@ -249,6 +385,7 @@
|
|
|
249
385
|
function open() {
|
|
250
386
|
if (!root) build();
|
|
251
387
|
if (isOpen()) return;
|
|
388
|
+
searchCache = {}; // a fresh open may see DB state the last one didn't
|
|
252
389
|
lastFocus = document.activeElement;
|
|
253
390
|
root.hidden = false;
|
|
254
391
|
document.documentElement.classList.add('palette-open');
|
|
@@ -261,6 +398,9 @@
|
|
|
261
398
|
|
|
262
399
|
function close() {
|
|
263
400
|
if (!isOpen()) return;
|
|
401
|
+
if (searchTimer) { clearTimeout(searchTimer); searchTimer = null; }
|
|
402
|
+
if (searchAbort) { searchAbort.abort(); searchAbort = null; }
|
|
403
|
+
searchSeq++; // invalidate any pending/in-flight search
|
|
264
404
|
root.hidden = true;
|
|
265
405
|
document.documentElement.classList.remove('palette-open');
|
|
266
406
|
if (lastFocus && lastFocus.focus) lastFocus.focus();
|
|
@@ -297,5 +437,5 @@
|
|
|
297
437
|
if (kbd) kbd.textContent = isMac ? '⌘K' : 'Ctrl K';
|
|
298
438
|
}
|
|
299
439
|
|
|
300
|
-
window.OTBPalette = { open: open, close: close, isOpen: isOpen, compute: compute };
|
|
440
|
+
window.OTBPalette = { open: open, close: close, isOpen: isOpen, compute: compute, contentEntry: contentEntry };
|
|
301
441
|
})();
|
|
@@ -50,7 +50,7 @@ const IDEA_COLS = IDEA_COL_NAMES.join(', ');
|
|
|
50
50
|
//
|
|
51
51
|
// THE DOOR IS READ OFF THE SESSION, not claimed in the body. A web cookie is the
|
|
52
52
|
// hall (the ideas form, the sky's composer), a device-flow token is the CLI
|
|
53
|
-
// (capture.js, spark.js), anything else (a
|
|
53
|
+
// (capture.js, spark.js), anything else (a program on the port) is
|
|
54
54
|
// the API — so a filer cannot say they came through a door they did not. The one
|
|
55
55
|
// value a body may ADD is 'planning': a planning session's capture is a CLI
|
|
56
56
|
// filing no session can tell apart, so capture.js --via planning names it. The
|
|
@@ -224,17 +224,17 @@ async function resolveClaim({ claimId, builderId, outcome, notesMd, valueSummary
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
// Task #1048 (ADR 0046 follow-up): record the builder's sandbox self-approval
|
|
227
|
-
// as a durable fact on the task. When a
|
|
227
|
+
// as a durable fact on the task. When a builder reviews a staged
|
|
228
228
|
// game change on their sandbox and approves shipping it (TTY 'y', or an
|
|
229
229
|
// explicit --approved re-run on the dominant non-TTY path), ship.js threads
|
|
230
230
|
// { stamp, url, method } through this resolve call. We stamp `by` + `at`
|
|
231
231
|
// SERVER-SIDE (never trust the client for identity or time) and persist the
|
|
232
232
|
// blob to tasks.sandbox_review. Only on a 'shipped' resolve carrying a
|
|
233
|
-
// payload — laptop ships, non-game diffs,
|
|
233
|
+
// payload — laptop ships, non-game diffs, --skip-stage,
|
|
234
234
|
// and un-approved re-runs send nothing, leaving the column NULL.
|
|
235
235
|
if (outcome === 'shipped' && sandboxReview && typeof sandboxReview === 'object' && sandboxReview.stamp) {
|
|
236
236
|
// Sanitize the client-supplied fields (defense in depth): the builder's
|
|
237
|
-
//
|
|
237
|
+
// machine controls stamp/url/method, so clamp lengths, whitelist `method`, and
|
|
238
238
|
// accept only an https:// URL. This keeps a forged/oversized value — or a
|
|
239
239
|
// future stored-XSS string like a `javascript:` URL rendered in the hall —
|
|
240
240
|
// out of the persisted record. `by`/`at` stay SERVER-stamped (never
|
|
@@ -1098,7 +1098,7 @@ async function confirmTask({ taskId, builderId }, deps = {}) {
|
|
|
1098
1098
|
// first-class way to KILL a task that is no longer relevant. The only routes to
|
|
1099
1099
|
// status='abandoned' were offboarding a builder (offboardBuilder, which seals
|
|
1100
1100
|
// each open claim) and a raw SQL UPDATE — so stale/moot tasks sat in 'ready' as
|
|
1101
|
-
// claimable noise (the
|
|
1101
|
+
// claimable noise (the teardown chores #819/#822/#827 and the dup tasks
|
|
1102
1102
|
// #907/#926 that other sessions had already shipped, all retired 2026-06-13).
|
|
1103
1103
|
// This is the missing transition.
|
|
1104
1104
|
//
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
// GDS server-mediated branch publish (ADR 0055 / task 1025).
|
|
2
2
|
//
|
|
3
3
|
// WHY THIS EXISTS
|
|
4
|
-
// A
|
|
5
|
-
//
|
|
6
|
-
// GH_TOKEN — infra/builder-box-cloud-init.yaml scrubs the clone token), and its
|
|
7
|
-
// baked GDS session is box-scoped (deny-by-default, #919/ADR 0053). CI self-
|
|
4
|
+
// A checkout with NO GitHub push credential by design (credential-less HTTPS
|
|
5
|
+
// origin, no gh, no GH_TOKEN) cannot publish in `ci` deploy mode. CI self-
|
|
8
6
|
// deploy (#859/ADR 0042) fixed only the LAST leg (Actions deploys main->prod);
|
|
9
|
-
//
|
|
7
|
+
// such a checkout still can't get its branch onto GitHub to open the PR.
|
|
10
8
|
//
|
|
11
|
-
// THE FIX (owner-chosen: "the
|
|
12
|
-
// a THIN git bundle to the GDS over a RE-AUTHED
|
|
9
|
+
// THE FIX (owner-chosen: "the checkout holds nothing"). The checkout uploads its
|
|
10
|
+
// branch as a THIN git bundle to the GDS over a RE-AUTHED cli session; the server
|
|
13
11
|
// pushes the branch to GitHub + opens the PR + enables auto-merge using a SERVER-
|
|
14
|
-
// side push credential. The
|
|
15
|
-
// model is preserved. The credential is scoped to a single repo (Contents:write +
|
|
12
|
+
// side push credential. The checkout never holds a GitHub push credential, so the
|
|
13
|
+
// #919 model is preserved. The credential is scoped to a single repo (Contents:write +
|
|
16
14
|
// Pull-requests:write + Actions:read) and CANNOT push to branch-protected `main`
|
|
17
15
|
// nor bypass required checks — a leaked server credential can only land code on a
|
|
18
16
|
// feature branch behind the same checks every PR faces.
|
|
@@ -30,8 +28,8 @@
|
|
|
30
28
|
// This module is the server seam: push a bundle, open/find the PR, enable auto-
|
|
31
29
|
// merge, report status. All git runs through execFile arg-arrays (shell:false,
|
|
32
30
|
// the #681 injection discipline); the credentialed URL is built in-process and
|
|
33
|
-
// NEVER persisted to a remote nor echoed on an error path (the
|
|
34
|
-
//
|
|
31
|
+
// NEVER persisted to a remote nor echoed on an error path (the "one-shot URL,
|
|
32
|
+
// scrub the credential" pattern). The GitHub API calls are raw
|
|
35
33
|
// global fetch (no Octokit — fetch is used throughout auth.js), header shape
|
|
36
34
|
// mirrors auth.js fetchGithubUser.
|
|
37
35
|
|
|
@@ -574,7 +572,7 @@ async function enableAutoMergeDetailed({ nodeId }, deps = {}) {
|
|
|
574
572
|
}
|
|
575
573
|
|
|
576
574
|
// getPublishStatus — report the PR + prod-deploy state so ship.js can poll
|
|
577
|
-
// WITHOUT gh on the
|
|
575
|
+
// WITHOUT gh on the checkout. Returns:
|
|
578
576
|
// { pr_state, merged, merge_sha, deploy_status, deploy_probe } where
|
|
579
577
|
// pr_state ∈ open|closed|none,
|
|
580
578
|
// deploy_status ∈ pending|success|none|unconfigured|unavailable
|
|
@@ -811,7 +809,7 @@ function prReferencesTask(status, taskId) {
|
|
|
811
809
|
|
|
812
810
|
// task 1002645 — locate a task's PR by the "task <id>: …" TITLE convention, for when
|
|
813
811
|
// the reconciler cannot derive the branch. The sweep derives claude/<worktree_name>
|
|
814
|
-
// (the
|
|
812
|
+
// (the conventional name), but a laptop/wave ship pushes its own branch name
|
|
815
813
|
// (worktree-<worktree_name>), so the derived branch finds no PR and the sweep's whole
|
|
816
814
|
// PR path — the mergeIfGreen nudge (the ONLY green-PR merger where native auto-merge
|
|
817
815
|
// is repo-disallowed), conflict heal, land proof — never engages; the task strands at
|
|
@@ -972,7 +970,7 @@ function mergeBlockReason(s) {
|
|
|
972
970
|
const bad = s.checks.filter(_BAD_CHECK);
|
|
973
971
|
if (bad.length) return `failing_check:${bad[0].name}`;
|
|
974
972
|
// Red, and the failing check is one of the nulled nodes: say the list is blind
|
|
975
|
-
// rather than guess "review or pending", which sent a
|
|
973
|
+
// rather than guess "review or pending", which sent a ship retrying (task 1003988).
|
|
976
974
|
if (s.checks_unreadable && ['FAILURE', 'ERROR'].includes(s.rollup_state)) return 'failing_check_unreadable';
|
|
977
975
|
return 'blocked_required_review_or_pending_check';
|
|
978
976
|
}
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
// "strands at confirmed" failure class.
|
|
5
5
|
//
|
|
6
6
|
// WHY THIS EXISTS
|
|
7
|
-
// A server-mediated publish (ADR 0055) is ASYNCHRONOUS: a
|
|
7
|
+
// A server-mediated publish (ADR 0055) is ASYNCHRONOUS: a checkout uploads its
|
|
8
8
|
// branch, the server pushes + opens the PR + arms auto-merge, then GitHub runs
|
|
9
|
-
// the required checks, merges, and Actions deploys to prod. The
|
|
9
|
+
// the required checks, merges, and Actions deploys to prod. The uploader's ship.js
|
|
10
10
|
// only POLLS for ~9 min (pollServerPublish) and then EXITS, printing "it flips to
|
|
11
11
|
// shipped on its own". But the confirmed→shipped transition lived in exactly ONE
|
|
12
12
|
// place — the GET /tasks/:id/publish-status reconcile-on-read (routes/tasks.js).
|
|
13
13
|
// That flip can ONLY happen while a client keeps issuing that GET. Real CI (check
|
|
14
|
-
// queue + merge queue + deploy) routinely finishes AFTER the
|
|
14
|
+
// queue + merge queue + deploy) routinely finishes AFTER the client has stopped
|
|
15
15
|
// polling, so nothing ever re-checks and the task row freezes at 'confirmed'
|
|
16
16
|
// forever — even though prod is live. Every prior point-fix (1025/1027/1037/1041/
|
|
17
17
|
// 1045/1046/1049/1050/1052/1068) compensated for a symptom of this one gap:
|
|
18
|
-
// "the
|
|
18
|
+
// "the client must stay alive and keep polling to finish its OWN ship."
|
|
19
19
|
//
|
|
20
20
|
// THE FIX. Move the last mile to the SERVER, where it belongs. A periodic sweep
|
|
21
21
|
// (this module) finds every 'confirmed' task, derives its branch from the latest
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// succeeded?), and on a confirmed land runs the EXACT db.shipTask +
|
|
24
24
|
// ship-broadcast.postShipBroadcast the publish-status route already runs. The land
|
|
25
25
|
// no longer depends on the shipper's CLI being alive — it works for EVERY builder,
|
|
26
|
-
// on a
|
|
26
|
+
// on a laptop or a desktop session, and retroactively heals strands left
|
|
27
27
|
// by a session that exited early. Idempotent: shipTask throws TASK_NOT_CONFIRMED
|
|
28
28
|
// if a client poll (or a prior sweep) already flipped it, so a sweep racing the
|
|
29
29
|
// route never double-ships nor double-broadcasts.
|
|
@@ -333,7 +333,7 @@ async function runSweep(deps = {}) {
|
|
|
333
333
|
|
|
334
334
|
// task 1002645 — the per-branch PR path (merge nudge → conflict/stale heal →
|
|
335
335
|
// publish-status), extracted so it can run TWICE for one task: first on the
|
|
336
|
-
// derived claude/<worktree_name> branch (the
|
|
336
|
+
// derived claude/<worktree_name> branch (the conventional name), then — when
|
|
337
337
|
// that finds no PR — on the REAL branch adopted from the task's own PR
|
|
338
338
|
// (findTaskPullRequest). Mutates the sweep counters via closure; returns
|
|
339
339
|
// { status, prNotLanded }.
|
|
@@ -605,7 +605,7 @@ async function runSweep(deps = {}) {
|
|
|
605
605
|
}
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
// task 1002645 — the claude/<worktree_name> derivation is
|
|
608
|
+
// task 1002645 — the claude/<worktree_name> derivation is only ONE convention;
|
|
609
609
|
// a laptop/wave ship pushes its own branch name (worktree-<worktree_name>), so the
|
|
610
610
|
// derived branch finds no PR and the merge nudge / conflict heal / land proof all
|
|
611
611
|
// sat idle while the task stranded at 'confirmed' (wave PRs 34/35/36 — native
|
|
@@ -768,8 +768,8 @@ async function runSweep(deps = {}) {
|
|
|
768
768
|
logFn(
|
|
769
769
|
`[publish-reconciler] task ${r.task_id} (${landNote}) landed → shipped`
|
|
770
770
|
);
|
|
771
|
-
// The
|
|
772
|
-
// secret
|
|
771
|
+
// The checkout that published cannot post the Discord broadcast (no webhook
|
|
772
|
+
// secret). The server holds the secrets, so it fires it here,
|
|
773
773
|
// exactly ONCE on the confirmed→shipped transition. Best-effort: never let
|
|
774
774
|
// a broadcast hiccup turn a successful reconcile into a failure.
|
|
775
775
|
try {
|
|
@@ -827,7 +827,7 @@ async function runSweep(deps = {}) {
|
|
|
827
827
|
|
|
828
828
|
// task 1366 — server-side session-cost reconcile, piggybacked on the same 5-min
|
|
829
829
|
// sweep. System 1's client cost hook silently skips the MAJORITY of sessions (a
|
|
830
|
-
//
|
|
830
|
+
// cowork VM / overnight agent runs with no GDS token, so the
|
|
831
831
|
// SessionEnd hook quiet-skips), so a server pass re-prices the model_usage we
|
|
832
832
|
// ALREADY stored on session_records into a cost_log row — keyed on the SAME
|
|
833
833
|
// (source, source_ref) the client would use, so logCost's ON CONFLICT DO NOTHING
|
|
@@ -624,7 +624,7 @@ module.exports = function buildTasksRouter() {
|
|
|
624
624
|
// confirmed→shipped flip and throws TASK_NOT_CONFIRMED on a repeat, so
|
|
625
625
|
// whichever entry point wins the transition is the only one that reaches
|
|
626
626
|
// this line — the emit fires exactly ONCE per ship across all three
|
|
627
|
-
// paths. The server is thus the SINGLE ship broadcaster (the
|
|
627
|
+
// paths. The server is thus the SINGLE ship broadcaster (the laptop
|
|
628
628
|
// local post in scripts/gds/ship.js was removed as a duplicate, task
|
|
629
629
|
// 1749). Best-effort: emitAsync isolates listener errors so a broadcast
|
|
630
630
|
// hiccup can never turn a successful ship into a 500.
|
|
@@ -1108,9 +1108,9 @@ module.exports = function buildTasksRouter() {
|
|
|
1108
1108
|
// ==========================================================================
|
|
1109
1109
|
// Server-mediated branch publish (ADR 0055 / task 1025)
|
|
1110
1110
|
// --------------------------------------------------------------------------
|
|
1111
|
-
// A
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1111
|
+
// A checkout with NO GitHub push credential (credential-less origin, #919)
|
|
1112
|
+
// cannot `git push` + `gh pr create`, so in `ci` deploy mode ship.js uploads
|
|
1113
|
+
// its branch as a THIN
|
|
1114
1114
|
// git bundle here; the server pushes it + opens the PR + enables auto-merge
|
|
1115
1115
|
// using its own push credential — a GitHub App installation token (preferred,
|
|
1116
1116
|
// task 1028) or the GITHUB_PUSH_TOKEN PAT fallback; single repo, Contents:write
|
|
@@ -1255,7 +1255,7 @@ module.exports = function buildTasksRouter() {
|
|
|
1255
1255
|
|
|
1256
1256
|
// GET /tasks/:id/publish-status?branch=<branch> — poll the PR + prod-deploy
|
|
1257
1257
|
// state for a server-mediated publish so ship.js can confirm the land WITHOUT
|
|
1258
|
-
// gh on the
|
|
1258
|
+
// gh on the checkout. Ownership-gated like the POST. Derives state live from GitHub.
|
|
1259
1259
|
// `branch` is optional (task 1003764): without it the server finds the task's own PR
|
|
1260
1260
|
// (both publish paths title it "task <id>: …") and the answer names the `branch` it
|
|
1261
1261
|
// used and its `branch_source`, so the check agents are sent to instead of git probes
|
|
@@ -1302,9 +1302,9 @@ module.exports = function buildTasksRouter() {
|
|
|
1302
1302
|
try {
|
|
1303
1303
|
const shipped = await db.shipTask({ taskId: id, builderId: req.builder.id, landAssurance: RECONCILE_PROOF }); // ADR 0184
|
|
1304
1304
|
reconciled = true;
|
|
1305
|
-
// task 1045: the
|
|
1306
|
-
// it holds no webhook secret
|
|
1307
|
-
//
|
|
1305
|
+
// task 1045: the checkout that published can't post the Discord
|
|
1306
|
+
// broadcast — it holds no webhook secret, so such a ship was silently
|
|
1307
|
+
// skipping the public announcement. The server holds
|
|
1308
1308
|
// the secrets and just confirmed the deploy, so it fires the broadcast
|
|
1309
1309
|
// here — exactly ONCE, on the confirmed→shipped transition. shipTask
|
|
1310
1310
|
// throws TASK_NOT_CONFIRMED on a repeat poll, so this never re-posts.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// ONE generator, two render paths, both PURE:
|
|
11
11
|
// renderMonospaceCard(model) — alignment-proof left-rule text. The CLIs ALWAYS
|
|
12
|
-
// print this, so headless / cron
|
|
12
|
+
// print this, so headless / cron runs and feeds without the in-feed
|
|
13
13
|
// visualization tool still get a consistent summary.
|
|
14
14
|
// renderCardHtml(model) — the populated, branded HTML the skills render in
|
|
15
15
|
// the chat feed via the visualization tool (show_widget). Brand tokens come
|
|
@@ -181,8 +181,8 @@ function isPubliclyViewable(task) {
|
|
|
181
181
|
// The CANONICAL prefix (src/bongos/api-prefix.js) is hard-coded rather than
|
|
182
182
|
// imported: this lib is deliberately core-free so it unit-tests with a Buffer and
|
|
183
183
|
// a temp dir, and the doorway does not publish the prefix. It is safe to hard-code
|
|
184
|
-
// because BOTH prefixes are permanent — '/api/gds' is a forever alias for
|
|
185
|
-
//
|
|
184
|
+
// because BOTH prefixes are permanent — '/api/gds' is a forever alias for older
|
|
185
|
+
// CLIs and the off-box status mirror — so a URL stored under either
|
|
186
186
|
// keeps resolving, and every renderer's guard accepts both.
|
|
187
187
|
const SERVE_PREFIX = '/api/bongos/task-visuals';
|
|
188
188
|
function urlForName(name) {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
//
|
|
33
33
|
// RENDER / STORED-XSS (#575, audit 2026-06-19) — memory content is stored RAW
|
|
34
34
|
// and is NEVER emitted as HTML. Every endpoint here returns it as JSON
|
|
35
|
-
// (res.json — content as a string field), and the only consumers are the CLI
|
|
36
|
-
//
|
|
35
|
+
// (res.json — content as a string field), and the only consumers are the CLI
|
|
36
|
+
// memory sync (it writes the strings to the local filesystem) and the
|
|
37
37
|
// Archon-only forensic reads (also JSON). An audit of every web surface
|
|
38
38
|
// (modules/hall-ui/public/, modules/status-ui/public/) found NO code path that interpolates
|
|
39
39
|
// memory content into the DOM: there is no markdown→HTML renderer in the client
|
|
@@ -249,8 +249,9 @@ module.exports = function ssoRoutes() {
|
|
|
249
249
|
//
|
|
250
250
|
// resolveSession is the RAW resolver (so we can redirect rather than 401), so it
|
|
251
251
|
// does NOT apply requireBuilder's deny gates — we must. sessionUsableForFederation
|
|
252
|
-
// rejects a deactivated account, a
|
|
253
|
-
// trust boundary), and service principals: none may mint a
|
|
252
|
+
// rejects a deactivated account, a session of an undeclared source (outside
|
|
253
|
+
// the ADR 0016 trust boundary), and service principals: none may mint a
|
|
254
|
+
// federated identity code.
|
|
254
255
|
let session;
|
|
255
256
|
try {
|
|
256
257
|
session = await resolveSession(req);
|
|
@@ -137,7 +137,7 @@ async function countPlaced(db, shapes, { excludeId = null } = {}) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// readLocalMeminfo — THIS host's /proc/meminfo, for the surfaces that run on the
|
|
140
|
-
// control plane itself. Null (not a throw) where it does not exist, e.g. a
|
|
140
|
+
// control plane itself. Null (not a throw) where it does not exist, e.g. a laptop
|
|
141
141
|
// that is not Linux — the summary then reads 'default' and says so.
|
|
142
142
|
function readLocalMeminfo() {
|
|
143
143
|
try { return fs.readFileSync('/proc/meminfo', 'utf8'); } catch { return null; }
|
|
@@ -351,8 +351,9 @@ function defaultDomainForSlug(slug, apex) {
|
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
// Labels the platform itself occupies (or has planned) directly under the instance
|
|
354
|
-
// apex, plus the
|
|
355
|
-
// so a project claiming one of these
|
|
354
|
+
// apex, plus the `term-` / `sandbox-` hostname prefixes older per-builder machines
|
|
355
|
+
// took. The runner's DNS write is an UPSERT, so a project claiming one of these
|
|
356
|
+
// would overwrite platform records — refuse at
|
|
356
357
|
// the API layer instead (task 1002704; the guard applies to auto-assigned AND
|
|
357
358
|
// bring-your-own hostnames, on create and on attach). Anything deeper than one
|
|
358
359
|
// label under the apex is also refused: it breaks the wildcard-cert rule (one
|
|
@@ -1133,7 +1134,7 @@ async function setInstanceStatus(db, id, status, patch = {}) {
|
|
|
1133
1134
|
|
|
1134
1135
|
// Enqueue a provision/teardown intent. Idempotent: the partial unique index
|
|
1135
1136
|
// ux_provisioning_intents_open makes a second open enqueue collide (23505); we
|
|
1136
|
-
// then return the already-open intent with created:false
|
|
1137
|
+
// then return the already-open intent with created:false.
|
|
1137
1138
|
async function enqueueIntent(db, instanceId, action, requestedBy, targetVersion = null) {
|
|
1138
1139
|
await require('./control-plane-guard').assertIntentAllowed(db, instanceId, action); // ADR 0293 D2; required HERE because this file is at the size ratchet
|
|
1139
1140
|
try {
|