@bongos/core 1.19.149 → 1.19.151
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 +16 -16
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +3 -0
- package/docs/architecture.md +1 -1
- package/modules/hall-ui/public/goals-page.js +4 -0
- package/modules/hall-ui/public/hall-kit.js +62 -0
- package/modules/hall-ui/public/task.js +8 -0
- package/modules/hall-ui/public/work.css +15 -0
- package/modules/hall-ui/public/work.js +4 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/box.js +27 -0
- package/src/module-api.js +5 -1
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.151",
|
|
6
|
+
"core_contract": "1.19.151",
|
|
7
|
+
"source_commit": "6ffcec970cdf3d4340b66e42edb33a49257fd4c5",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-16T14:
|
|
9
|
+
"built_at": "2026-08-16T14:50:02.406Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 304,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1579,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "0b11b762ff3c5e42925fc4aa0d743f3bd3f3b77e4340e4d00013c3cefe401540",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -1177,7 +1177,7 @@
|
|
|
1177
1177
|
{
|
|
1178
1178
|
"path": "docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md",
|
|
1179
1179
|
"mode": "0000644",
|
|
1180
|
-
"sha256": "
|
|
1180
|
+
"sha256": "6f3312ff0cdfdb824626375ca4e3e517e21f5f0f7087f579feee40e836bb3cc1"
|
|
1181
1181
|
},
|
|
1182
1182
|
{
|
|
1183
1183
|
"path": "docs/adr/0144-federated-single-logout-backchannel.md",
|
|
@@ -1352,7 +1352,7 @@
|
|
|
1352
1352
|
{
|
|
1353
1353
|
"path": "docs/architecture.md",
|
|
1354
1354
|
"mode": "0000644",
|
|
1355
|
-
"sha256": "
|
|
1355
|
+
"sha256": "49747f42b1229ee8a2ecc54c317fc7173a91a4340ddfe2033e1a78b0f6ed1482"
|
|
1356
1356
|
},
|
|
1357
1357
|
{
|
|
1358
1358
|
"path": "docs/branding-contract.md",
|
|
@@ -3162,7 +3162,7 @@
|
|
|
3162
3162
|
{
|
|
3163
3163
|
"path": "modules/hall-ui/public/goals-page.js",
|
|
3164
3164
|
"mode": "0000644",
|
|
3165
|
-
"sha256": "
|
|
3165
|
+
"sha256": "f24cfdca48676d882cfa51754fcf1519ade4646a760cb2be1db71fbc27571bca"
|
|
3166
3166
|
},
|
|
3167
3167
|
{
|
|
3168
3168
|
"path": "modules/hall-ui/public/goals.css",
|
|
@@ -3197,7 +3197,7 @@
|
|
|
3197
3197
|
{
|
|
3198
3198
|
"path": "modules/hall-ui/public/hall-kit.js",
|
|
3199
3199
|
"mode": "0000644",
|
|
3200
|
-
"sha256": "
|
|
3200
|
+
"sha256": "4d5ec8f334fc00f23e81fe21283b7ea2fb173b253c638a08ef48b97111cc654f"
|
|
3201
3201
|
},
|
|
3202
3202
|
{
|
|
3203
3203
|
"path": "modules/hall-ui/public/hall-widgets.js",
|
|
@@ -3407,7 +3407,7 @@
|
|
|
3407
3407
|
{
|
|
3408
3408
|
"path": "modules/hall-ui/public/task.js",
|
|
3409
3409
|
"mode": "0000644",
|
|
3410
|
-
"sha256": "
|
|
3410
|
+
"sha256": "ac6bfa459924a43005625e9c2655a5a1397c941dfa0727594bb25f930180d36a"
|
|
3411
3411
|
},
|
|
3412
3412
|
{
|
|
3413
3413
|
"path": "modules/hall-ui/public/watch.css",
|
|
@@ -3427,7 +3427,7 @@
|
|
|
3427
3427
|
{
|
|
3428
3428
|
"path": "modules/hall-ui/public/work.css",
|
|
3429
3429
|
"mode": "0000644",
|
|
3430
|
-
"sha256": "
|
|
3430
|
+
"sha256": "e5dd2fd84b31174d10bb6558920cbbc9e34b1776d775db3c69518810a1c7b96c"
|
|
3431
3431
|
},
|
|
3432
3432
|
{
|
|
3433
3433
|
"path": "modules/hall-ui/public/work.html",
|
|
@@ -3437,7 +3437,7 @@
|
|
|
3437
3437
|
{
|
|
3438
3438
|
"path": "modules/hall-ui/public/work.js",
|
|
3439
3439
|
"mode": "0000644",
|
|
3440
|
-
"sha256": "
|
|
3440
|
+
"sha256": "8ee189def6ded1c8669212d7233f57948ac58bf23ad8277422996ffd8914e537"
|
|
3441
3441
|
},
|
|
3442
3442
|
{
|
|
3443
3443
|
"path": "modules/ideas/CLAUDE.md",
|
|
@@ -4187,12 +4187,12 @@
|
|
|
4187
4187
|
{
|
|
4188
4188
|
"path": "package-lock.json",
|
|
4189
4189
|
"mode": "0000644",
|
|
4190
|
-
"sha256": "
|
|
4190
|
+
"sha256": "739468093ef4eab580a28b005fd816fadee7f7cf1facf9db6f9f5cf9cc352b36"
|
|
4191
4191
|
},
|
|
4192
4192
|
{
|
|
4193
4193
|
"path": "package.json",
|
|
4194
4194
|
"mode": "0000644",
|
|
4195
|
-
"sha256": "
|
|
4195
|
+
"sha256": "6f3fd6f5b9780d2e612b95a4d1ba8cba840b1d5c79be73aaa034d435a8da344b"
|
|
4196
4196
|
},
|
|
4197
4197
|
{
|
|
4198
4198
|
"path": "public-docs/index.html",
|
|
@@ -4622,7 +4622,7 @@
|
|
|
4622
4622
|
{
|
|
4623
4623
|
"path": "scripts/gds/box.js",
|
|
4624
4624
|
"mode": "0000644",
|
|
4625
|
-
"sha256": "
|
|
4625
|
+
"sha256": "c8a79e6541a2e26e590bd8ee1bbf31cb0f7300c53dbd30c241f4c1f04a2af7bf"
|
|
4626
4626
|
},
|
|
4627
4627
|
{
|
|
4628
4628
|
"path": "scripts/gds/build-bongos.js",
|
|
@@ -5782,7 +5782,7 @@
|
|
|
5782
5782
|
{
|
|
5783
5783
|
"path": "src/module-api.js",
|
|
5784
5784
|
"mode": "0000644",
|
|
5785
|
-
"sha256": "
|
|
5785
|
+
"sha256": "ac7dd33de47d132bc68dca844d0d7f0992a5fa14249516e84c32c21960b14ba6"
|
|
5786
5786
|
},
|
|
5787
5787
|
{
|
|
5788
5788
|
"path": "src/module-loader/catalog.js",
|
|
@@ -39,3 +39,6 @@ Stand up a **cloudbongos-native box plane** alongside (not replacing) the legacy
|
|
|
39
39
|
2. **Cloudflare token scope.** Provision warned the ADR 0053 cookie-strip Transform Rule was **not** applied — `CLOUDFLARE_API_TOKEN` lacks "Transform Rules" edit scope on the cloudbongos.com zone. Add the scope (or apply the rule once in the dashboard) **before** other builders get boxes on `*.cloudbongos.com`, so platform cookies (`cookieDomain=<redacted> aren't sent to box subdomains.
|
|
40
40
|
3. **Cosmetic `otb-` prefix.** Tunnel names (`otb-term-<login>`) and the droplet name (`otb-box-<login>`) are still hardcoded in `box-onboard.js`/`box.js`. Harmless (fleet separation is by tag, and DNS is per-zone), but a branding leak worth parameterizing.
|
|
41
41
|
4. **Phase 4 — retire the legacy example plane** (owner decision) — **part (a) DONE in task 1002248 (2026-07-15).** Pre-checked read-only that the legacy fleet was empty (0 `otb-builder-box` droplets on DigitalOcean; cohost intent queue `ran=0`), then `systemctl disable --now` on the four `cohost-box-*` timers (intent-runner / idle-suspend / drift-reconcile / dormant-reclaim) on the control-plane host — now `disabled`/`inactive`; the native `cloudbongos-box-*` timers were left `enabled`/`active`. **Part (b) — disabling the dev-box module on example.com itself — NOT reachable from the control plane, flagged to owner:** example's web app + instance checkout are not co-located on the cloudbongos droplet (no local checkout, no ssh alias) and the local example GDS token is expired, so a durable module-disable needs owner access to example's own config/host. Harmless meanwhile: with the runners stopped there is no cost/security impact — worst case is a box request on the legacy instance that now hangs unfulfilled.
|
|
42
|
+
5. **The control plane silently froze on core 1.19.2 — FIXED (task 1002738, 2026-08-16).** The four `cloudbongos-box-*` units were installed with `WorkingDirectory=/home/lars/cloudbongos` (§2 above), and `infra/core-node.sh` resolves the core from *that* root's `node_modules/@bongos/core` — pinned to the vendored `bongos-core-1.19.2.tgz`. The web app meanwhile moved to the npm registry pin and rode the nightly patch sweep to **1.19.146**. The two never converged, because `config/update-subscriptions.json` lists exactly ONE instance (`/home/lars/cloudbongos-instance`) and the box root was never added. Net effect: **every park/destroy/provision decision on this fleet was made by a core ~145 patches old**, so the whole point of tasks 1002726/1002727 — an idle box *parks* instead of being destroyed — was landed in `main`, released, deployed to the web app, and still **never executed**: `cmdSweepIdle` in 1.19.2 calls `deprovisionOne`. Nothing surfaced it; a shipped, released, deployed fix was simply inert.
|
|
43
|
+
**Fix.** A systemd drop-in on each of the four units re-points `WorkingDirectory` at `/home/lars/cloudbongos-instance` — the subscribed root the nightly sweep keeps current — and runs `node node_modules/@bongos/core/scripts/gds/box.js <cmd> --apply` directly. Same shape as the drop-ins already applied to `{selfhost,cohost}-provision-intent-runner`. The box lifecycle now inherits every core release with no second upgrade path to remember. The stale root is NOT retired: it still vendors `infra/` (the cloud-init + bring-up scripts the core package does not ship — follow-up 1 / ADR 0150), supplied to the units via `CLOUDBONGOS_BOX_INFRA_DIR=/home/lars/cloudbongos/infra`, the documented escape hatch that is candidate [#1](http://localhost:3000/builders#/task/1) in `boxInfraCandidates()`. Verified: all four commands dry-ran **identically** on old and new core before cutover (0 idle, 0 intents, DO⇄DB consistent, 0 dormant), then ran live under the drop-ins; the dormant sweep's new `warn >11d` window in the journal is the 1002727 code proving itself present.
|
|
44
|
+
**Why it stayed invisible, and the guard.** The sweep banner printed the *threshold* but never the *code*, so a stale plane looked identical to a current one in `journalctl`; diagnosing it needed a filesystem dig across two instance roots. `box.js` now prints one provenance line on every invocation — `box.js · running from @bongos/core@<version> · instance <root>` — naming the package it actually resolved. A frozen plane now announces itself in its own logs.
|
package/docs/architecture.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
- **Domain:** `example.com` — registered at Cloudflare Registrar. DNS A records (apex + `www`) point to the droplet at `REDACTED_IP`. Cloudflare proxy is **ON (orange cloud)**. CF terminates TLS at the edge; the **Cloudflare Origin Certificate** handles the CF↔origin leg (valid through 2041-05-04). A `status` subdomain is added at PMS-V1 deploy time (see [`docs/pms-v1-deploy.md`](pms-v1-deploy.md)) with the same proxy/cert posture; Origin Cert needs `*.example.com` SAN. Real client IPs come in via `CF-Connecting-IP` / `X-Forwarded-For`, wired into Caddy's `trusted_proxies static` (the Cloudflare IP ranges) in the `infra/Caddyfile` global options block.
|
|
13
13
|
- **Production droplet (co-hosting box):** DigitalOcean, hostname `example`, IPv4 `REDACTED_IP`, IPv6 `REDACTED_IP`. NYC3, Premium Intel 2 GB / 1 vCPU / 60 GB NVMe. Ubuntu 24.04 LTS. ~$14/mo. Hosts the OTB game + co-hosted project instances (`emersonian-circles.`, `staging.`).
|
|
14
|
-
- **Cloud Bongos control-plane droplet (NEW 2026-07-05, [ADR 0126](adr/<redacted>.md), task [#2063](https://example.com/builders#/task/2063)):** DigitalOcean, hostname `cloudbongos`, IPv4 `REDACTED_IP`, NYC3, `s-1vcpu-2gb` (2 GB / 1 vCPU), Ubuntu 24.04, ~$12/mo, tag `cloudbongos-control`. Runs **cloudbongos.com + `builders.` + `status.`** (`src/platform-server.js`, Cloud Bongos brand, port 3002, own `cloudbongos` DB, checkout `/home/lars/cloudbongos
|
|
14
|
+
- **Cloud Bongos control-plane droplet (NEW 2026-07-05, [ADR 0126](adr/<redacted>.md), task [#2063](https://example.com/builders#/task/2063)):** DigitalOcean, hostname `cloudbongos`, IPv4 `REDACTED_IP`, NYC3, `s-1vcpu-2gb` (2 GB / 1 vCPU), Ubuntu 24.04, ~$12/mo, tag `cloudbongos-control`. Runs **cloudbongos.com + `builders.` + `status.`** (`src/platform-server.js`, Cloud Bongos brand, port 3002, own `cloudbongos` DB, checkout `/home/lars/cloudbongos-instance`, the root subscribed to the nightly core-patch sweep) and is the **sole token-holding control plane** — a SECOND root, `/home/lars/cloudbongos`, survives only to vendor `infra/` (the box cloud-init the core package does not ship, ADR 0150); it is not subscribed, so nothing may resolve core code from it (task 1002738, [ADR 0144](adr/<redacted>.md) follow-up 5) — `/etc/cloudbongos/box.env` holds the account-wide Cloudflare token (IP-locked to this box) + the DigitalOcean token; this is where `provision.js` provisions instance subdomains. **Structurally separate SSH** (own `cloudbongos_ed25519` key). IPv6 egress disabled so the IPv4-locked CF token can't be spuriously rejected. cloudbongos.com was previously a co-tenant on the co-hosting box; that unit was decommissioned at cutover. **Follow-ups:** deploy automation (task [#2064](https://example.com/builders#/task/2064)) + fully stripping cloud tokens off the co-hosting box (task [#2065](https://example.com/builders#/task/2065)).
|
|
15
15
|
- **Server stack:** Node.js 22 LTS · PostgreSQL 16 · Caddy 2.11.2. `example.service` (systemd) runs `node server.js` as user `lars` on `127.0.0.1:3000`; restart-on-failure.
|
|
16
16
|
- **Hardening:** `lars` user with passwordless sudo; SSH key-only (root login disabled, password auth disabled); ufw allows 22/80/443; fail2ban active; unattended-upgrades enabled; timezone `America/New_York`.
|
|
17
17
|
- **TLS:** Origin cert at `/etc/caddy/origin.crt` + `/etc/caddy/origin.key` for the example apexes on the co-hosting box. **cloudbongos.com's apexes moved to the dedicated control-plane droplet** ([ADR 0126](adr/<redacted>.md)) with their own copy of the Cloudflare Origin Cert (SAN `*.cloudbongos.com, cloudbongos.com`) at `/etc/caddy/cloudbongos-origin.{crt,key}` on `REDACTED_IP`. Cloudflare SSL/TLS mode = Full (strict). Renewal is a non-issue for ~15 years. **Provisioned instance hosts use a different, coexisting posture** ([ADR 0111](adr/<redacted>.md) §4 / task P4): Caddy **on-demand ACME** (Let's Encrypt) via the global `on_demand_tls { ask … }` block, gated to hostnames the `provisioning` module actually stood up (`/api/bongos/provisioning/tls-check`). Their DNS records are **proxied=false (grey cloud)** so the ACME challenge reaches Caddy — the opposite of the origin-cert apexes; do not enable the proxy on them or renewal breaks silently ([ADR 0003](adr/<redacted>.md)). Per-instance site snippets live in `/etc/caddy/sites/*.caddy` (`import`ed by `infra/Caddyfile`). **Tradeoff (accepted, SEC review of task 1945):** `proxied=false` publishes the box's real origin IP in public DNS for every co-tenant provisioned hostname — defeating, for those names, the Cloudflare-proxy IP-hiding the apexes enjoy (a co-tenant host resolves to the same `REDACTED_IP` that serves the main game). This is inherent to on-demand ACME (the challenge must hit the origin) and acceptable under the budget/trust model; prefer a **dedicated droplet** (its own IP) for any instance expected to draw hostile attention.
|
|
@@ -529,6 +529,9 @@
|
|
|
529
529
|
const touchesChip = touches.length
|
|
530
530
|
? `<span class="task__touches" title="${escapeHtml(touches.join('\n'))}">✎ ${touches.length} file${touches.length === 1 ? '' : 's'}</span>`
|
|
531
531
|
: '';
|
|
532
|
+
// task 1002955: same age chip the board renders — reviewing a goal for stale
|
|
533
|
+
// work is the case that needed it most.
|
|
534
|
+
const ageChip = window.OTBKit.ageChipHtml(t.created_at);
|
|
532
535
|
const claim = state.claimsByTask.get(id);
|
|
533
536
|
const isReady = t.status === 'ready' && !!state.me;
|
|
534
537
|
const mineActive = t.status === 'active' && claim && sameId(claim.builder_id, selfId());
|
|
@@ -569,6 +572,7 @@
|
|
|
569
572
|
<span>${minutesText}</span>
|
|
570
573
|
<span>${cpmText}</span>
|
|
571
574
|
${touchesChip}
|
|
575
|
+
${ageChip}
|
|
572
576
|
</div>
|
|
573
577
|
</div>
|
|
574
578
|
<div class="task__credits">
|
|
@@ -417,6 +417,66 @@
|
|
|
417
417
|
};
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
// ---- age (task 1002955) ------------------------------------------------------
|
|
421
|
+
// How old is this row? The board, the goal Work card and the task panel all
|
|
422
|
+
// render a task's kind/estimate/credits but never said when it was written, so
|
|
423
|
+
// a queue of 40 ready tasks read as equally fresh whether it was filed
|
|
424
|
+
// yesterday or three months ago. Kit-level because the card markup is
|
|
425
|
+
// duplicated across work.js + goals-page.js (task 1002463) and a third
|
|
426
|
+
// hand-rolled date helper is exactly what this file exists to prevent.
|
|
427
|
+
|
|
428
|
+
// Coarse buckets on purpose: past a week nobody cares about the hour, and the
|
|
429
|
+
// exact day is one hover away (ageChipHtml puts it on title=). Anything in the
|
|
430
|
+
// future — a clock skew, a seeded row — clamps to "new" rather than "-3d".
|
|
431
|
+
const AGE_STALE_DAYS = 30;
|
|
432
|
+
function fmtAge(iso) {
|
|
433
|
+
if (!iso) return '';
|
|
434
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
435
|
+
if (!Number.isFinite(ms)) return '';
|
|
436
|
+
const days = Math.floor(ms / 86400000);
|
|
437
|
+
if (days < 0) return 'new';
|
|
438
|
+
if (days === 0) return 'today';
|
|
439
|
+
if (days === 1) return '1d';
|
|
440
|
+
if (days < 14) return `${days}d`;
|
|
441
|
+
if (days < 60) return `${Math.floor(days / 7)}w`;
|
|
442
|
+
// Clamped at 11: 30-day months overshoot, so days 330-364 would otherwise
|
|
443
|
+
// print "12mo" immediately before the year bucket takes over at 365.
|
|
444
|
+
if (days < 365) return `${Math.min(11, Math.floor(days / 30))}mo`;
|
|
445
|
+
const years = Math.floor(days / 365);
|
|
446
|
+
const rem = Math.floor((days % 365) / 30);
|
|
447
|
+
return rem ? `${years}y ${rem}mo` : `${years}y`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Absolute date for the tooltip — locale-formatted, no time (the age chip is
|
|
451
|
+
// about which week something was filed, not which minute).
|
|
452
|
+
function fmtAbsDate(iso) {
|
|
453
|
+
if (!iso) return '';
|
|
454
|
+
const d = new Date(iso);
|
|
455
|
+
if (Number.isNaN(d.getTime())) return '';
|
|
456
|
+
try {
|
|
457
|
+
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
|
458
|
+
} catch (_) { return d.toISOString().slice(0, 10); }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function ageDays(iso) {
|
|
462
|
+
if (!iso) return null;
|
|
463
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
464
|
+
return Number.isFinite(ms) ? Math.floor(ms / 86400000) : null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ageChipHtml(iso, opts?) → the meta-row chip. Past AGE_STALE_DAYS it gains
|
|
468
|
+
// .task__age--stale, so scanning a goal's queue for work that has been sitting
|
|
469
|
+
// is a glance rather than an API call. `label` names the date in the tooltip
|
|
470
|
+
// ("Created" by default) so the same chip can carry other timestamps.
|
|
471
|
+
function ageChipHtml(iso, { label = 'Created' } = {}) {
|
|
472
|
+
const text = fmtAge(iso);
|
|
473
|
+
if (!text) return '';
|
|
474
|
+
const days = ageDays(iso);
|
|
475
|
+
const stale = days != null && days >= AGE_STALE_DAYS;
|
|
476
|
+
const abs = fmtAbsDate(iso);
|
|
477
|
+
return `<span class="task__age${stale ? ' task__age--stale' : ''}" title="${esc(label)} ${esc(abs)}">${esc(text)}</span>`;
|
|
478
|
+
}
|
|
479
|
+
|
|
420
480
|
// ---- empty state / skeleton / collapsible ------------------------------------
|
|
421
481
|
|
|
422
482
|
// One standard empty pattern: a short line + at most one action link. An
|
|
@@ -465,6 +525,8 @@
|
|
|
465
525
|
makeLedger, makeFilterBar, makeTabs, makeSectionRail,
|
|
466
526
|
// states
|
|
467
527
|
emptyStateHtml, skeletonRows, collapsibleCard,
|
|
528
|
+
// age (task 1002955)
|
|
529
|
+
fmtAge, fmtAbsDate, ageChipHtml, AGE_STALE_DAYS,
|
|
468
530
|
};
|
|
469
531
|
|
|
470
532
|
// Node-require-safe (the dom-utils.js pattern): assignment is the only
|
|
@@ -184,6 +184,14 @@
|
|
|
184
184
|
if (t.est_minutes) meta.push(`${t.est_minutes}m`);
|
|
185
185
|
parts.push(`<p class="deeplink-card__meta">${meta.join(' · ')}</p>`);
|
|
186
186
|
|
|
187
|
+
// task 1002955: when was this written? The panel showed a shipped date but
|
|
188
|
+
// never a created one, so an untouched task carried no age at all. Absolute
|
|
189
|
+
// date first (the panel has the room the card chip doesn't), age after it.
|
|
190
|
+
if (t.created_at) {
|
|
191
|
+
const age = fmtRel(t.created_at);
|
|
192
|
+
parts.push(`<p class="deeplink-card__note">Created ${escapeHtml(fmtDate(t.created_at))}${age ? ` — ${escapeHtml(age)}` : ''}.</p>`);
|
|
193
|
+
}
|
|
194
|
+
|
|
187
195
|
if (t.goal_id) {
|
|
188
196
|
parts.push(`<p class="td-goal">Goal: <a href="/goals#/goal/${encodeURIComponent(t.goal_id)}">Goal #${escapeHtml(String(t.goal_id))}</a></p>`);
|
|
189
197
|
}
|
|
@@ -66,6 +66,21 @@
|
|
|
66
66
|
cursor: help;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/* How old the task is (task 1002955, OTBKit.ageChipHtml). Quiet by default —
|
|
70
|
+
it's orientation, not a call to act — but a task past the stale threshold
|
|
71
|
+
steps up from faint to soft+bold so a queue that has been sitting reads at a
|
|
72
|
+
glance instead of needing the API. The exact date is on the tooltip, hence
|
|
73
|
+
`cursor: help`. Deliberately NOT --warn: that token is a dark amber paired
|
|
74
|
+
with --warn-weak behind it, and as bare text it loses contrast on the dark
|
|
75
|
+
theme's card. --ink-soft steps up legibly in both. */
|
|
76
|
+
.task__age {
|
|
77
|
+
color: var(--ink-faint);
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
cursor: help;
|
|
81
|
+
}
|
|
82
|
+
.task__age--stale { color: var(--ink-soft); font-weight: 600; }
|
|
83
|
+
|
|
69
84
|
.task__tag--newcomer {
|
|
70
85
|
background: var(--ok-weak);
|
|
71
86
|
color: var(--ok);
|
|
@@ -360,6 +360,9 @@
|
|
|
360
360
|
const touchesChip = touches.length
|
|
361
361
|
? `<span class="task__touches" title="${escapeHtml(touches.join('\n'))}">✎ ${touches.length} file${touches.length === 1 ? '' : 's'}</span>`
|
|
362
362
|
: '';
|
|
363
|
+
// task 1002955: how old is this task? A ready queue reads as equally fresh
|
|
364
|
+
// without it; the exact date rides the tooltip.
|
|
365
|
+
const ageChip = window.OTBKit.ageChipHtml(t.created_at);
|
|
363
366
|
const selected = selection.claim.has(String(t.id));
|
|
364
367
|
return `
|
|
365
368
|
<div class="task${selected ? ' task--selected' : ''}" data-task-id="${t.id}">
|
|
@@ -380,6 +383,7 @@
|
|
|
380
383
|
<span>${minutesText}</span>
|
|
381
384
|
<span>${cpmText}</span>
|
|
382
385
|
${touchesChip}
|
|
386
|
+
${ageChip}
|
|
383
387
|
</div>
|
|
384
388
|
</div>
|
|
385
389
|
<div class="task__credits">
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.151",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.151",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.151",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/scripts/gds/box.js
CHANGED
|
@@ -1447,11 +1447,38 @@ Named browser-terminal tunnel (#771, ADR 0038 — OPT-IN, default OFF):
|
|
|
1447
1447
|
// never imports it, it is always spawned) it exposes the orchestration fns so the
|
|
1448
1448
|
// dry-run/live contract can be exercised with injected fakes (#1034). The guard
|
|
1449
1449
|
// keeps a require() from running the CLI + calling process.exit.
|
|
1450
|
+
// provenance() — WHICH core is making this fleet decision, and from which root.
|
|
1451
|
+
//
|
|
1452
|
+
// Task 1002738: the four cloudbongos-box-* units ran with WorkingDirectory pointed at
|
|
1453
|
+
// a root whose vendored @bongos/core was 1.19.2 while the web app had long since moved
|
|
1454
|
+
// to 1.19.146 — so every park/destroy/provision decision was made by a core ~145
|
|
1455
|
+
// patches behind, including a cmdSweepIdle that still DESTROYED an idle box. Nothing
|
|
1456
|
+
// in the journal said so: the sweep banner reports the THRESHOLD but never the code.
|
|
1457
|
+
// Diagnosing it took a filesystem dig across two instance roots.
|
|
1458
|
+
//
|
|
1459
|
+
// One line, printed by every invocation, makes that failure self-announcing: a stale
|
|
1460
|
+
// plane now says its own version in `journalctl -u cloudbongos-box-idle-suspend`, next
|
|
1461
|
+
// to the instance root that supplied it. Cheap, unconditional, and it can only be
|
|
1462
|
+
// wrong if the package it reads is the package it ran.
|
|
1463
|
+
// Names the package REPO_ROOT resolved to, not just its version: installed, that is
|
|
1464
|
+
// `@bongos/core@1.19.148`; in a monolith checkout it is the instance's own package, and
|
|
1465
|
+
// saying so is the point — the line must never let a monolith masquerade as a core pin.
|
|
1466
|
+
function provenance() {
|
|
1467
|
+
let pkg = 'unknown';
|
|
1468
|
+
try {
|
|
1469
|
+
const p = require(path.join(REPO_ROOT, 'package.json'));
|
|
1470
|
+
pkg = `${p.name || 'unknown'}@${p.version || 'unknown'}`;
|
|
1471
|
+
} catch { /* a core with no readable package.json still runs — just say unknown */ }
|
|
1472
|
+
const inst = instanceConfig.resolveInstanceRootExplicit() || process.cwd();
|
|
1473
|
+
return `box.js · running from ${pkg} · instance ${inst}`;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1450
1476
|
async function main() {
|
|
1451
1477
|
const args = process.argv.slice(2);
|
|
1452
1478
|
const cmd = args[0];
|
|
1453
1479
|
APPLY = hasFlag(args, '--apply');
|
|
1454
1480
|
if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') { usage(); process.exit(0); }
|
|
1481
|
+
console.log(provenance());
|
|
1455
1482
|
|
|
1456
1483
|
const known = ['provision', 'onboard', 'park', 'wake', 'deprovision', 'run-intents', 'sweep-idle', 'sweep-dormant', 'reconcile', 'reconcile-drift', 'status'];
|
|
1457
1484
|
if (!known.includes(cmd)) { console.error(`unknown command: ${cmd}\n`); usage(); process.exit(2); }
|
package/src/module-api.js
CHANGED
|
@@ -725,8 +725,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
725
725
|
// landed since 1.19.147 with no explicit bump. run 31952592623. (task 1002620)
|
|
726
726
|
// 1.19.149 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
727
727
|
// landed since 1.19.148 with no explicit bump. run 31953207683. (task 1002620)
|
|
728
|
+
// 1.19.150 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
729
|
+
// landed since 1.19.149 with no explicit bump. run 31953504697. (task 1002620)
|
|
730
|
+
// 1.19.151 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
731
|
+
// landed since 1.19.150 with no explicit bump. run 31953827282. (task 1002620)
|
|
728
732
|
// ---------------------------------------------------------------------------
|
|
729
|
-
const CORE_VERSION = '1.19.
|
|
733
|
+
const CORE_VERSION = '1.19.151'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
730
734
|
|
|
731
735
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
732
736
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|