@bongos/core 1.24.68 → 1.24.69
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 +28 -23
- package/docs/adr/0029-status-page-outage-resilience.md +2 -2
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/core-release-pipeline.md +3 -3
- package/modules/lifecycle/merge-reach.js +15 -8
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +26 -0
- package/scripts/bongos/claim.js +16 -5
- package/scripts/bongos/deploy-divergence.js +5 -3
- package/src/instance-config.js +7 -10
- package/src/module-api.js +1 -1
- package/tests/agent_standing_rules.mjs +14 -0
- package/tests/claim_finish_hint.mjs +50 -0
- package/tests/deploy_divergence_line.mjs +5 -1
- package/tests/instance_config.mjs +32 -1
- package/tests/shell_hooks.mjs +7 -0
- package/tests/status_mirror_sync.mjs +22 -1
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/bongos/package-core.js",
|
|
5
|
-
"core_version": "1.24.
|
|
6
|
-
"core_contract": "1.24.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.24.69",
|
|
6
|
+
"core_contract": "1.24.69",
|
|
7
|
+
"source_commit": "68d624516bb357028ebcde65caf3b4a8fec22239",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-11-
|
|
9
|
+
"built_at": "2026-11-09T21:00:37.018Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 638,
|
|
13
13
|
"agent_docs_stubbed": 32,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 3905,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 4576,
|
|
20
|
+
"tree_sha256": "dbadf59ddc54d1764c6ad589df54766251762350991b5db66d08fb3283a5f471",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -557,7 +557,7 @@
|
|
|
557
557
|
{
|
|
558
558
|
"path": "docs/adr/0029-status-page-outage-resilience.md",
|
|
559
559
|
"mode": "0000644",
|
|
560
|
-
"sha256": "
|
|
560
|
+
"sha256": "199fc3e775f32f46a658294bbbe83783a7455cff2b554f8680f4235b1ae0ece1"
|
|
561
561
|
},
|
|
562
562
|
{
|
|
563
563
|
"path": "docs/adr/0030-builders-hall-live-updates.md",
|
|
@@ -3007,7 +3007,7 @@
|
|
|
3007
3007
|
{
|
|
3008
3008
|
"path": "docs/module-api-changelog.md",
|
|
3009
3009
|
"mode": "0000644",
|
|
3010
|
-
"sha256": "
|
|
3010
|
+
"sha256": "65351a8f07adc711a0045deb60cad8493c943702365a855d6400748fbdf2108d"
|
|
3011
3011
|
},
|
|
3012
3012
|
{
|
|
3013
3013
|
"path": "docs/modules-contract.md",
|
|
@@ -3127,7 +3127,7 @@
|
|
|
3127
3127
|
{
|
|
3128
3128
|
"path": "docs/recipes/core-release-pipeline.md",
|
|
3129
3129
|
"mode": "0000644",
|
|
3130
|
-
"sha256": "
|
|
3130
|
+
"sha256": "09cb01eeb9aa79bca4380b11498c05dd30e4985358e8cb8fdec7497b2a9581b6"
|
|
3131
3131
|
},
|
|
3132
3132
|
{
|
|
3133
3133
|
"path": "docs/recipes/failed-ship-recovery-proof.md",
|
|
@@ -8157,7 +8157,7 @@
|
|
|
8157
8157
|
{
|
|
8158
8158
|
"path": "modules/lifecycle/merge-reach.js",
|
|
8159
8159
|
"mode": "0000644",
|
|
8160
|
-
"sha256": "
|
|
8160
|
+
"sha256": "422b941660edc139d0353cccb110c8d130827c02200511da372d23456f133e51"
|
|
8161
8161
|
},
|
|
8162
8162
|
{
|
|
8163
8163
|
"path": "modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql",
|
|
@@ -11902,12 +11902,12 @@
|
|
|
11902
11902
|
{
|
|
11903
11903
|
"path": "package-lock.json",
|
|
11904
11904
|
"mode": "0000644",
|
|
11905
|
-
"sha256": "
|
|
11905
|
+
"sha256": "7965aa4c685c6bdd12a6710eb50550a823662415b832362a07f41b2b8ac7d2af"
|
|
11906
11906
|
},
|
|
11907
11907
|
{
|
|
11908
11908
|
"path": "package.json",
|
|
11909
11909
|
"mode": "0000644",
|
|
11910
|
-
"sha256": "
|
|
11910
|
+
"sha256": "fb8d2aa51f2449ffe9ba7c21b564fdc36c288f5fd55737f16b245dd60430d780"
|
|
11911
11911
|
},
|
|
11912
11912
|
{
|
|
11913
11913
|
"path": "public-docs/index.html",
|
|
@@ -11927,7 +11927,7 @@
|
|
|
11927
11927
|
{
|
|
11928
11928
|
"path": "release-notes.json",
|
|
11929
11929
|
"mode": "0000644",
|
|
11930
|
-
"sha256": "
|
|
11930
|
+
"sha256": "325bd7b65c334113a2600e606108a7f2597e9fa0ba045ef76170d3d4907af92c"
|
|
11931
11931
|
},
|
|
11932
11932
|
{
|
|
11933
11933
|
"path": "scripts/bongos/CLAUDE.md",
|
|
@@ -12182,7 +12182,7 @@
|
|
|
12182
12182
|
{
|
|
12183
12183
|
"path": "scripts/bongos/claim.js",
|
|
12184
12184
|
"mode": "0000644",
|
|
12185
|
-
"sha256": "
|
|
12185
|
+
"sha256": "377cd8c4aa3bff219bf2d071cdfe0f918a3ceb30b775d8c881f329f36173f1e0"
|
|
12186
12186
|
},
|
|
12187
12187
|
{
|
|
12188
12188
|
"path": "scripts/bongos/claude-materialize.js",
|
|
@@ -12292,7 +12292,7 @@
|
|
|
12292
12292
|
{
|
|
12293
12293
|
"path": "scripts/bongos/deploy-divergence.js",
|
|
12294
12294
|
"mode": "0000644",
|
|
12295
|
-
"sha256": "
|
|
12295
|
+
"sha256": "82b9f7657d6fcd5ed8bd79e4e7104e3b960b649eaf122d6ab871f48b86494ad9"
|
|
12296
12296
|
},
|
|
12297
12297
|
{
|
|
12298
12298
|
"path": "scripts/bongos/deploy-main-audit-gate.sh",
|
|
@@ -15867,7 +15867,7 @@
|
|
|
15867
15867
|
{
|
|
15868
15868
|
"path": "src/instance-config.js",
|
|
15869
15869
|
"mode": "0000644",
|
|
15870
|
-
"sha256": "
|
|
15870
|
+
"sha256": "b123541932f206894a652aebf64a13ed0794a4b44e68936a11d65bf49c7a2fc9"
|
|
15871
15871
|
},
|
|
15872
15872
|
{
|
|
15873
15873
|
"path": "src/link-preview.js",
|
|
@@ -15877,7 +15877,7 @@
|
|
|
15877
15877
|
{
|
|
15878
15878
|
"path": "src/module-api.js",
|
|
15879
15879
|
"mode": "0000644",
|
|
15880
|
-
"sha256": "
|
|
15880
|
+
"sha256": "610ccbeab694b699af5e1a2e5142760bf95ce1c3c8affe30d076d36272390410"
|
|
15881
15881
|
},
|
|
15882
15882
|
{
|
|
15883
15883
|
"path": "src/module-loader/catalog.js",
|
|
@@ -16087,7 +16087,7 @@
|
|
|
16087
16087
|
{
|
|
16088
16088
|
"path": "tests/agent_standing_rules.mjs",
|
|
16089
16089
|
"mode": "0000644",
|
|
16090
|
-
"sha256": "
|
|
16090
|
+
"sha256": "08edffc3a673b01249586330d7cb0fbb4f31d7dfaaadc80864191899fedb5545"
|
|
16091
16091
|
},
|
|
16092
16092
|
{
|
|
16093
16093
|
"path": "tests/agents_authoring.mjs",
|
|
@@ -16814,6 +16814,11 @@
|
|
|
16814
16814
|
"mode": "0000644",
|
|
16815
16815
|
"sha256": "8b937f463b8f03dba8c67047d8aca9be84a00839f4821e00ee5d31a96a6bead9"
|
|
16816
16816
|
},
|
|
16817
|
+
{
|
|
16818
|
+
"path": "tests/claim_finish_hint.mjs",
|
|
16819
|
+
"mode": "0000644",
|
|
16820
|
+
"sha256": "a8c2e33617c23d9f6584f2901a7ad9f77f0ed5588b2dd219ba79b3c4c805b4b0"
|
|
16821
|
+
},
|
|
16817
16822
|
{
|
|
16818
16823
|
"path": "tests/claim_from_task_and_home.mjs",
|
|
16819
16824
|
"mode": "0000644",
|
|
@@ -17397,7 +17402,7 @@
|
|
|
17397
17402
|
{
|
|
17398
17403
|
"path": "tests/deploy_divergence_line.mjs",
|
|
17399
17404
|
"mode": "0000644",
|
|
17400
|
-
"sha256": "
|
|
17405
|
+
"sha256": "b4067239ab71d3aaf3fa7e05fd4a1aaee3938a975970265f3f1679eeab838062"
|
|
17401
17406
|
},
|
|
17402
17407
|
{
|
|
17403
17408
|
"path": "tests/deploy_handoff.mjs",
|
|
@@ -19517,7 +19522,7 @@
|
|
|
19517
19522
|
{
|
|
19518
19523
|
"path": "tests/instance_config.mjs",
|
|
19519
19524
|
"mode": "0000644",
|
|
19520
|
-
"sha256": "
|
|
19525
|
+
"sha256": "355349970d4a6f903a55bebacdde11eaca4283b22c8ee8266b346a9ecdd8a8ca"
|
|
19521
19526
|
},
|
|
19522
19527
|
{
|
|
19523
19528
|
"path": "tests/instance_db_migrated_on_code_move.mjs",
|
|
@@ -21862,7 +21867,7 @@
|
|
|
21862
21867
|
{
|
|
21863
21868
|
"path": "tests/shell_hooks.mjs",
|
|
21864
21869
|
"mode": "0000644",
|
|
21865
|
-
"sha256": "
|
|
21870
|
+
"sha256": "efafe5abb508560bae1287e6e811f9efb907280b1c032e5bb07e6027f6b17add"
|
|
21866
21871
|
},
|
|
21867
21872
|
{
|
|
21868
21873
|
"path": "tests/shell_stat_portability.mjs",
|
|
@@ -22267,7 +22272,7 @@
|
|
|
22267
22272
|
{
|
|
22268
22273
|
"path": "tests/status_mirror_sync.mjs",
|
|
22269
22274
|
"mode": "0000644",
|
|
22270
|
-
"sha256": "
|
|
22275
|
+
"sha256": "9112bd164ed3eaf73826677a646e37819d8400c2f857cb2ff668ae9a8173d5d7"
|
|
22271
22276
|
},
|
|
22272
22277
|
{
|
|
22273
22278
|
"path": "tests/status_page_chrome.mjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0029 — Status-page outage resilience: measure from outside, host off-box, prevent at the source
|
|
2
2
|
|
|
3
|
-
- **Status:** Accepted. **Slice 1 (external prober) built this session** under task **[#565](https://example.com/builders#/task/565)**: `.github/workflows/status-probe.yml` + `scripts/status-probe.mjs` + this ADR. **Follow-up B (page off-box) has since SHIPPED** — `status.example.com` is served off the droplet from GitHub Pages (repo `example-owner/example-status`)
|
|
3
|
+
- **Status:** Accepted. **Slice 1 (external prober) built this session** under task **[#565](https://example.com/builders#/task/565)**: `.github/workflows/status-probe.yml` + `scripts/status-probe.mjs` + this ADR. **Follow-up B (page off-box) has since SHIPPED** — `status.example.com` is served off the droplet from GitHub Pages (repo `example-owner/example-status`). **No workflow syncs it** (task 1005131): `sync-status-mirror.yml` is not in this repo, so the page updates only when someone runs `node scripts/status-mirror-sync.js --repo-dir <mirror-checkout> --push` by hand. Incident-backfill (follow-up C) is **not yet built**. **Since removed (task 1004858, 2026-10-25):** the in-process `uptime-poller.js` (nothing started it), the `status-probe.mjs` prober (its workflow was already gone) and the `/public/uptime` feed — the file references below are history.
|
|
4
4
|
- **Date:** 2026-06-03
|
|
5
5
|
- **Deciders:** Lars Kristensen (Archon), Claude
|
|
6
6
|
- **Track:** `internal` (the status dashboard / The Watch)
|
|
@@ -29,7 +29,7 @@ An **external prober runs on GitHub-hosted runners** — off our DigitalOcean in
|
|
|
29
29
|
- `.github/workflows/status-probe.yml` — `*/5` cron + `workflow_dispatch`; publishes the result to an **orphan `status-data` branch** (off-box, web-readable, survives a droplet outage); **fails the job when the site is confirmed down**, which emails the repo owner — the alert.
|
|
30
30
|
|
|
31
31
|
### 2. Host the page off-box (follow-up B — SHIPPED)
|
|
32
|
-
`status.example.com` is now served off the droplet from **GitHub Pages** (repo `example-owner/example-status`),
|
|
32
|
+
`status.example.com` is now served off the droplet from **GitHub Pages** (repo `example-owner/example-status`), published by hand with `scripts/status-mirror-sync.js` (no workflow runs it — task 1005131); the page no longer shares fate with the product. Original plan, for the record:
|
|
33
33
|
Move `public-status/` to free static hosting **separate from the droplet** (Cloudflare Pages or GitHub Pages). The page shell + the **uptime/incident section** are served off-box and read the `status-data` source, so they stay truthful during an outage. The rich GDS panels (treasury, grades, progress) keep loading client-side from `example.com/api/gds/public/*` and **degrade gracefully** to "origin unreachable" when the droplet is down (requires adding CORS to those public routes). Then repoint `status.example.com` DNS at the off-box host.
|
|
34
34
|
|
|
35
35
|
### 3. Prevent at the source + alert (blocker [#6](https://example.com/builders#/blocker/6))
|
|
@@ -3390,5 +3390,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3390
3390
|
landed since 1.24.66 with no explicit bump. run 42689728958. (task 1002620)
|
|
3391
3391
|
1.24.68 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3392
3392
|
landed since 1.24.67 with no explicit bump. run 42692736161. (task 1002620)
|
|
3393
|
+
1.24.69 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3394
|
+
landed since 1.24.68 with no explicit bump. run 42698123714. (task 1002620)
|
|
3393
3395
|
---------------------------------------------------------------------------
|
|
3394
3396
|
```
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| Who | Gets a merged change | How to check |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| **cloudbongos.com** — the projects hub and the builders hall | only when a **published core release** carries it **and the hub's pin moves** —
|
|
7
|
+
| **cloudbongos.com** — the projects hub and the builders hall | only when a **published core release** carries it **and the hub's pin moves** — its patch-channel subscription moves the pin to a new patch release on its own (task 1005020); a new minor release waits for the owner | its `/version`; the ship card's **Deploy** row reads it: *"live at `<sha>` (includes this merge \| not yet)"* |
|
|
8
8
|
| **Projects hosted on the platform** | only when a **published core release** carries it (auto-published per merge, [ADR 0161](../adr/0161-publish-on-merge.md)) **and that project's pin moves** — a patch-channel subscription or a core-upgrade | that project's `/version` `coreVersion`; the ship card's **Projects** row states the rule |
|
|
9
9
|
|
|
10
10
|
So a shipped task is live nowhere until a release carries it, and then only on an instance whose pin has moved — the hub included. The release itself waits on a green integration lane: the publish lane's integration gate refuses while `integration` on `main` is red. Never call work live until that instance's `/version` shows it. (This table used to say the hub redeploys from `main` on every merge, within minutes. It never did; on 2026-10-25 the hub had served one build through 200+ merges — task 1004873.) The wording has one source, [`modules/lifecycle/merge-reach.js`](../../modules/lifecycle/merge-reach.js); the ship card, `deploy-divergence.js` and the SessionStart line all print it (task 1004682). `/recall who sees a merge` finds the same rule as learning 1000292, which corrects learning 1000291 (that one still says the hub redeploys on every merge; the API has no way to edit a learning).
|
|
@@ -297,7 +297,7 @@ If live is **at or past** `<VER>`, stop and pick again. On 2026-09-17 a paste pr
|
|
|
297
297
|
|
|
298
298
|
What each piece is for — each guards a failure that actually happened:
|
|
299
299
|
|
|
300
|
-
- **`BONGOS_INSTANCE_ROOT`, not `CLOUDBONGOS_INSTANCE_ROOT`.** The legacy spelling still works but prints a deprecation warning three times per run
|
|
300
|
+
- **`BONGOS_INSTANCE_ROOT`, not `CLOUDBONGOS_INSTANCE_ROOT`.** The legacy spelling still works but prints a deprecation warning three times per run (it is still read, but will stop being read in a future core release); the new spelling is verified in this exact line (2026-09-17, twice).
|
|
301
301
|
- **The token lives in root-only `/etc/cloudbongos/npm.env`, and is handed DOWN to `lars`.** This is the step that used to be documented wrongly, so read it before improvising. `NPM_TOKEN` is **not** in lars’s environment, **not** in the interactive profile, and **not** in `/home/lars/.npmrc` — all three probed empty (2026-08-17). An earlier version of this runbook grepped `~/.npmrc` and therefore aborted on its own `[ -n "$NPM_TOKEN" ]` guard every time. Worse, it could not have worked even if that file were populated: the project `.npmrc` says `_authToken=<redacted>, which **overrides** lars’s user `~/.npmrc`, so the variable is the only channel. Hence the shape above: `set -a; . /etc/cloudbongos/npm.env; set +a` reads it as **root** (mode 600), and `--preserve-env=NPM_TOKEN` hands it to the unprivileged `lars` — exactly what systemd does for the sweep unit (`User=lars` + `EnvironmentFile=`). Without it npm falls back to the **public** registry, 404s on the private `@bongos/core`, and rolls back cleanly having taught you nothing.
|
|
302
302
|
- **Never put the token in the command line — `sudo` logs the full argv (task 1003218).** The shape above is safe *because* the token is read INSIDE the quoted script, so only the `grep` reaches argv. The tempting variant is not:
|
|
303
303
|
```
|
|
@@ -342,7 +342,7 @@ node scripts/bongos/deploy-divergence.js --url https://<instance-host>
|
|
|
342
342
|
It reports the two gaps **separately**, because they have different fixes:
|
|
343
343
|
|
|
344
344
|
- **UNRELEASED** — commits on `main` past the last `CORE_VERSION` bump. *No instance, the hub included, can take these at any pin* — no published version contains them yet. The publish lane (ADR 0161) normally carries a merge within minutes; a gap that stays means the lane is disarmed, red, or refused by its integration gate. The SessionStart line also says whether `main`'s latest merge is in a release yet.
|
|
345
|
-
- **UNDEPLOYED** — the live instance's `coreVersion` is behind `main`'s. A release exists it has not picked up: its pin has not moved (gate 3). On the hub that move is owner
|
|
345
|
+
- **UNDEPLOYED** — the live instance's `coreVersion` is behind `main`'s. A release exists it has not picked up: its pin has not moved (gate 3). On the hub the patch-channel sweep makes that move on its own; a gap that stays means the sweep is held (artist gate, failed health check) or the release is a new minor, which waits for the owner.
|
|
346
346
|
|
|
347
347
|
Exit codes: `0` aligned, `1` diverged, `2` undetermined.
|
|
348
348
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// modules/lifecycle/merge-reach.js — who sees a merge, and when. The ONE source of
|
|
2
|
-
// the sentence (task 1004682; corrected by
|
|
2
|
+
// the sentence (task 1004682; corrected by tasks 1004873 and 1005020).
|
|
3
3
|
//
|
|
4
4
|
// One path, for every instance — the hub included:
|
|
5
5
|
// 1. A merge to main is first carried by a PUBLISHED core release. The publish
|
|
6
6
|
// lane cuts one per merge (ADR 0161), but only while its integration gate is
|
|
7
7
|
// green; a red integration lane stalls every release behind it.
|
|
8
|
-
// 2. An instance gets that release only when its PIN moves.
|
|
9
|
-
// (cloudbongos.com: the projects hub and the builders hall)
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// 2. An instance gets that release only when its PIN moves. The hub
|
|
9
|
+
// (cloudbongos.com: the projects hub and the builders hall) is subscribed to
|
|
10
|
+
// the patch channel, so its update sweep moves the pin to a new patch release
|
|
11
|
+
// on its own — no person needed; only a new MINOR release (which the patch
|
|
12
|
+
// channel never takes) waits for the owner's Hall → Deploy. On a hosted
|
|
13
|
+
// project it is a patch-channel subscription or a core-upgrade.
|
|
12
14
|
//
|
|
13
15
|
// History: this file used to say the hub "redeploys from main on every merge,
|
|
14
16
|
// within minutes". It never did — the hub pins a published core like any other
|
|
@@ -16,7 +18,9 @@
|
|
|
16
18
|
// while sessions told the owner that work was live (task 1004873). Before that,
|
|
17
19
|
// deploy-divergence.js printed "Merging to main deploys nothing" (ADR 0137), false
|
|
18
20
|
// in the other direction. The ship card, the divergence report and the SessionStart
|
|
19
|
-
// line all read their wording from here, so they cannot drift apart again.
|
|
21
|
+
// line all read their wording from here, so they cannot drift apart again. Then
|
|
22
|
+
// task 1004873's wording called the hub's pin move "owner-gated", which was false
|
|
23
|
+
// for patches: cloudbongos.com moved to core 1.24.12 with no person (task 1005015).
|
|
20
24
|
//
|
|
21
25
|
// PURE and dependency-free: callers pass an already-sanitised reading (host, short
|
|
22
26
|
// sha, inclusion), never a raw network response — some of this text is injected
|
|
@@ -26,8 +30,11 @@
|
|
|
26
30
|
|
|
27
31
|
const HOSTED_LINE = 'Hosted projects: only when a published core release carries the change and their pin moves (patch-channel subscription or a core-upgrade).';
|
|
28
32
|
|
|
33
|
+
// How the hub's pin moves (task 1005020): by itself for a patch release, by the owner for a minor.
|
|
34
|
+
const HUB_PIN = 'its patch-channel subscription moves that pin on its own; a new minor release waits for the owner';
|
|
35
|
+
|
|
29
36
|
function hubRule(host) {
|
|
30
|
-
return `${host || 'The hub'} gets a merge only when a published core release carries it and its pin moves (
|
|
37
|
+
return `${host || 'The hub'} gets a merge only when a published core release carries it and its pin moves (${HUB_PIN})`;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
// includes: true (the live build carries this merge), false (not yet), null (cannot tell).
|
|
@@ -36,7 +43,7 @@ function hubLine({ host = null, liveShort = null, includes = null } = {}) {
|
|
|
36
43
|
const state = includes === true ? 'includes this merge'
|
|
37
44
|
: includes === false ? 'not yet'
|
|
38
45
|
: 'cannot tell yet whether it includes this merge';
|
|
39
|
-
return `${host || 'The hub'}: live at ${liveShort} (${state}) — it gets a merge only when a published core release carries it and its pin moves (
|
|
46
|
+
return `${host || 'The hub'}: live at ${liveShort} (${state}) — it gets a merge only when a published core release carries it and its pin moves (${HUB_PIN}).`;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
// Is main's latest merge in a published release yet? `tipShort` is a short sha,
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.69",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.69",
|
|
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.24.
|
|
3
|
+
"version": "1.24.69",
|
|
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/release-notes.json
CHANGED
|
@@ -11411,5 +11411,31 @@
|
|
|
11411
11411
|
"id": "1005132",
|
|
11412
11412
|
"text": "The design checker now catches text hidden under a dark overlay (the bug that made the components page unreadable in dark mode), instead of reporting it clean."
|
|
11413
11413
|
}
|
|
11414
|
+
],
|
|
11415
|
+
"1.24.69": [
|
|
11416
|
+
{
|
|
11417
|
+
"id": "1005113",
|
|
11418
|
+
"text": "In a project that installs Bongos from npm (like toast), the claim card now tells you to finish with 'npx bongos ship', a command that exists there, instead of a script path that doesn't."
|
|
11419
|
+
},
|
|
11420
|
+
{
|
|
11421
|
+
"id": "1005131",
|
|
11422
|
+
"text": "The status-page docs no longer claim an automatic sync that does not exist. They now say plainly that the off-site status page updates only when someone publishes it by hand, and a test keeps that claim honest."
|
|
11423
|
+
},
|
|
11424
|
+
{
|
|
11425
|
+
"id": "1005022",
|
|
11426
|
+
"text": "The 'you may have made a mistake' nudge no longer fires on automatic messages from other sessions or CI — only on a person's own corrections."
|
|
11427
|
+
},
|
|
11428
|
+
{
|
|
11429
|
+
"id": "1005020",
|
|
11430
|
+
"text": "Status messages now say plainly that cloudbongos.com picks up small (patch) releases on its own — no one has to move it by hand. Only a bigger (minor) release waits for the owner."
|
|
11431
|
+
},
|
|
11432
|
+
{
|
|
11433
|
+
"id": "1005042",
|
|
11434
|
+
"text": "The old-style settings warning no longer promises a removal date that already passed; it now says honestly the old names still work for now."
|
|
11435
|
+
},
|
|
11436
|
+
{
|
|
11437
|
+
"id": "1004949",
|
|
11438
|
+
"text": "When a shell command was blocked by one hook and another hook was missing, the warning about the missing hook vanished. Now both messages show."
|
|
11439
|
+
}
|
|
11414
11440
|
]
|
|
11415
11441
|
}
|
package/scripts/bongos/claim.js
CHANGED
|
@@ -763,10 +763,7 @@ async function main() {
|
|
|
763
763
|
// best-effort and never block the claim.
|
|
764
764
|
const shownLearningKeys = await printRelevantLearnings(task);
|
|
765
765
|
await printClaimRecall(task, { excludeKeys: shownLearningKeys });
|
|
766
|
-
console.log(
|
|
767
|
-
console.log(` node scripts/bongos/ship.js ${task.id} --notes "..." --summary "..."`);
|
|
768
|
-
console.log('Or to abandon:');
|
|
769
|
-
console.log(` node scripts/bongos/release.js ${task.id}`);
|
|
766
|
+
for (const line of finishHintLines(task.id)) console.log(line);
|
|
770
767
|
// V3.R13 (#237): post-claim nudge — for a newcomer, this becomes
|
|
771
768
|
// "Next: Ship thy first work" so the path forward is visible. Reuses the
|
|
772
769
|
// /me fetched above for the context pack (no second round-trip).
|
|
@@ -802,6 +799,20 @@ async function main() {
|
|
|
802
799
|
|
|
803
800
|
// Only auto-run when invoked directly (`node claim.js …`); a `require()` (the
|
|
804
801
|
// unit test) just imports formatClaimFailure without executing the claim flow.
|
|
802
|
+
// task 1005113 - claim card finish: the "When you finish" hint names the command that
|
|
803
|
+
// exists where this claim ran. A project that installs the core from npm has no
|
|
804
|
+
// scripts/bongos/ of its own — the CLI ships in node_modules/@bongos/core and runs as
|
|
805
|
+
// `npx bongos <verb>` — so a node path there points at nothing. isInstalledCore() is the
|
|
806
|
+
// one positive "this core sits under node_modules" check fitness already uses.
|
|
807
|
+
function finishHintLines(taskId, { installed } = {}) {
|
|
808
|
+
if (installed === undefined) {
|
|
809
|
+
try { installed = require('./fitness-lib').isInstalledCore(); } catch { installed = false; }
|
|
810
|
+
}
|
|
811
|
+
const ship = installed ? `npx bongos ship ${taskId}` : `node scripts/bongos/ship.js ${taskId}`;
|
|
812
|
+
const release = installed ? `npx bongos release ${taskId}` : `node scripts/bongos/release.js ${taskId}`;
|
|
813
|
+
return ['\nWhen you finish:', ` ${ship} --notes "..." --summary "..."`, 'Or to abandon:', ` ${release}`];
|
|
814
|
+
}
|
|
815
|
+
|
|
805
816
|
if (require.main === module) {
|
|
806
817
|
main().catch((err) => {
|
|
807
818
|
console.error('fatal:', err);
|
|
@@ -809,4 +820,4 @@ if (require.main === module) {
|
|
|
809
820
|
});
|
|
810
821
|
}
|
|
811
822
|
|
|
812
|
-
module.exports = { formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan, materializeModeSkills, printRoleLine, worktreeDetectionNote, liveStrandLeases, QUEUE_GATED_EXIT };
|
|
823
|
+
module.exports = { finishHintLines, formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan, materializeModeSkills, printRoleLine, worktreeDetectionNote, liveStrandLeases, QUEUE_GATED_EXIT };
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// modules/lifecycle/merge-reach.js): every instance, the hub included, gets a merge
|
|
6
6
|
// only when a published core release carries it AND that instance's pin moves. The
|
|
7
7
|
// publish lane cuts a release per merge (ADR 0161) only while the integration lane
|
|
8
|
-
// is green; on the hub (cloudbongos.com)
|
|
8
|
+
// is green; on the hub (cloudbongos.com) its patch-channel subscription moves the pin
|
|
9
|
+
// on its own (task 1005020), and only a new minor release waits for the owner.
|
|
9
10
|
// Two blanket claims have been wrong here: "Merging to main deploys nothing" (ADR
|
|
10
11
|
// 0137, false since ADR 0161) and "the hub redeploys from main on every merge"
|
|
11
12
|
// (never true — the hub pins a published core). Sessions repeated each to the owner.
|
|
@@ -335,8 +336,9 @@ function report(r, { quiet = false } = {}) {
|
|
|
335
336
|
}
|
|
336
337
|
if (r.behind) {
|
|
337
338
|
say(` ⚠ UNDEPLOYED — live is ${r.live.coreVersion}, main declares ${r.onMain}.`);
|
|
338
|
-
say(' A release exists that this instance has not picked up: its pin has not moved. On the hub
|
|
339
|
-
say(' owner
|
|
339
|
+
say(' A release exists that this instance has not picked up: its pin has not moved. On the hub its patch-channel');
|
|
340
|
+
say(' sweep moves it on its own (a new minor waits for the owner); on a hosted project it is its update channel or a');
|
|
341
|
+
say(' core-upgrade (docs/recipes/upgrading-the-core.md). A gap that stays means the sweep is held or failing.');
|
|
340
342
|
}
|
|
341
343
|
if (r.schemaBehind) {
|
|
342
344
|
const n = r.live.schemaPending;
|
package/src/instance-config.js
CHANGED
|
@@ -46,12 +46,10 @@ const LEGACY_DIRS = ['otb'];
|
|
|
46
46
|
// test that it FIRES rather than one that merely tolerates it.
|
|
47
47
|
const LEGACY_ENV_PREFIXES = ['CLOUDBONGOS', 'OTB', 'GDS', 'PMS'];
|
|
48
48
|
|
|
49
|
-
// The
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// removes the fallback and moves this constant's meaning to "already gone".
|
|
54
|
-
const LEGACY_ENV_SUNSET_RELEASE = '1.21';
|
|
49
|
+
// The deprecation warning names no removal release (task 1005042 - legacy env
|
|
50
|
+
// warning): it promised 1.21, the retirement (task 1003706 - C3d) was abandoned,
|
|
51
|
+
// and 1.24 still read the old names. Name a real release only in the diff that
|
|
52
|
+
// deletes the fallback.
|
|
55
53
|
|
|
56
54
|
// Lazy require (ADR 0108 §1 made this a two-way edge: branding.js now requires
|
|
57
55
|
// resolveCoreRoot()/resolveInstanceRoot() from this module, so a top-level
|
|
@@ -267,7 +265,7 @@ function warnLegacyEnv({ legacyName, canonicalName, shadowed, differs, log = con
|
|
|
267
265
|
: shadowed
|
|
268
266
|
? `${canonicalName} is also set and wins`
|
|
269
267
|
: `rename it to ${canonicalName}`;
|
|
270
|
-
log.warn(`[bongos] deprecated env ${legacyName} — ${fate}. The legacy spelling
|
|
268
|
+
log.warn(`[bongos] deprecated env ${legacyName} — ${fate}. The legacy spelling is still read for now, but will stop being read in a future core release.`);
|
|
271
269
|
return true;
|
|
272
270
|
}
|
|
273
271
|
|
|
@@ -282,11 +280,10 @@ function warnPackPrefix({ prefix, name, log = console } = {}) {
|
|
|
282
280
|
return true;
|
|
283
281
|
}
|
|
284
282
|
|
|
285
|
-
// NOT exported, deliberately: warnLegacyEnv
|
|
286
|
-
// ledger are internals. resolveEnv's injectable `warn` is the whole test seam a
|
|
283
|
+
// NOT exported, deliberately: warnLegacyEnv and the ledger are internals. resolveEnv's injectable `warn` is the whole test seam a
|
|
287
284
|
// caller needs, and a test of the DEFAULT path captures console.warn — which
|
|
288
285
|
// exercises the real wiring rather than a hand-called helper. Exporting them
|
|
289
|
-
// would also add
|
|
286
|
+
// would also add untraceable entries to the knip dead-code ratchet.
|
|
290
287
|
|
|
291
288
|
// resolveCoreRoot() / resolveInstanceRoot() — the ADR 0108 §1 configurable-root
|
|
292
289
|
// resolvers, extending this module's established zero-risk pattern from
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.69'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -112,6 +112,20 @@ t('the app\'s own status messages never trigger the slop check (task 1005023)',
|
|
|
112
112
|
assert.ok(conductor.evaluateTriggers({ prompt: "that's wrong, it's already live" }).some((x) => x.id === 'slop-check'), 'a real correction still fires');
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
t('side-session events, cross-session messages and CI events never trigger the slop check (task 1005022)', () => {
|
|
116
|
+
const words = "It's already live, not true, actually it works, you said it was slop";
|
|
117
|
+
for (const notice of [
|
|
118
|
+
`<side-sessions-event>Session bug-x ended a turn: ${words}</side-sessions-event>`,
|
|
119
|
+
`<cross-session-message from="local_abc">${words}</cross-session-message>`,
|
|
120
|
+
`<teammate-message teammate_id="t1">${words}</teammate-message>`,
|
|
121
|
+
`<ci-monitor-event pr="12">${words}</ci-monitor-event>`,
|
|
122
|
+
` \n<channel source="discord">${words}</channel>`,
|
|
123
|
+
]) {
|
|
124
|
+
assert.ok(!conductor.evaluateTriggers({ prompt: notice }).some((x) => x.id === 'slop-check'), notice);
|
|
125
|
+
}
|
|
126
|
+
assert.ok(conductor.evaluateTriggers({ prompt: "<b>that's wrong</b>, it's already live" }).some((x) => x.id === 'slop-check'), 'a person typing a tag still gets checked');
|
|
127
|
+
});
|
|
128
|
+
|
|
115
129
|
t('ordinary prompts do not trigger the slop check', () => {
|
|
116
130
|
for (const p of ['add a dark mode toggle', 'run the tests', 'what can I work on', 'make the button wider']) {
|
|
117
131
|
assert.ok(!conductor.evaluateTriggers({ prompt: p }).some((x) => x.id === 'slop-check'), p);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// tests/claim_finish_hint.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1005113 - claim card finish: the claim card's "When you finish" hint printed
|
|
4
|
+
// `node scripts/bongos/ship.js N` everywhere. In a project that installs the core from npm
|
|
5
|
+
// (toast), that path is not in the project — the CLI ships inside node_modules/@bongos/core
|
|
6
|
+
// and runs as `npx bongos ship N`. finishHintLines() picks the command for where the core
|
|
7
|
+
// is: a core checkout keeps the node path; an installed core gets the npx verbs.
|
|
8
|
+
//
|
|
9
|
+
// Run: node tests/claim_finish_hint.mjs
|
|
10
|
+
|
|
11
|
+
import { strict as assert } from 'node:assert';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { finishHintLines } = require('../scripts/bongos/claim.js');
|
|
16
|
+
|
|
17
|
+
let passed = 0;
|
|
18
|
+
let failed = 0;
|
|
19
|
+
function t(name, fn) {
|
|
20
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
21
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
t('installed core: ship + release hints use npx bongos, never a scripts/ path', () => {
|
|
25
|
+
const text = finishHintLines('1005113', { installed: true }).join('\n');
|
|
26
|
+
assert.match(text, /npx bongos ship 1005113 --notes "\.\.\." --summary "\.\.\."/);
|
|
27
|
+
assert.match(text, /npx bongos release 1005113/);
|
|
28
|
+
assert.doesNotMatch(text, /scripts\//);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
t('core checkout: hints keep the node scripts/bongos path', () => {
|
|
32
|
+
const text = finishHintLines('42', { installed: false }).join('\n');
|
|
33
|
+
assert.match(text, /node scripts\/bongos\/ship\.js 42 --notes/);
|
|
34
|
+
assert.match(text, /node scripts\/bongos\/release\.js 42/);
|
|
35
|
+
assert.doesNotMatch(text, /scripts\/gds\//);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
t('hint keeps its headings', () => {
|
|
39
|
+
const lines = finishHintLines('7', { installed: true });
|
|
40
|
+
assert.equal(lines[0], '\nWhen you finish:');
|
|
41
|
+
assert.ok(lines.includes('Or to abandon:'));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
t('defaults to detecting the core here (this checkout is not installed)', () => {
|
|
45
|
+
const text = finishHintLines('9').join('\n');
|
|
46
|
+
assert.match(text, /node scripts\/bongos\/ship\.js 9/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
50
|
+
if (failed) process.exit(1);
|
|
@@ -79,7 +79,11 @@ test('the line says who sees a merge, not a blanket rule in either direction', (
|
|
|
79
79
|
const s = line(behind);
|
|
80
80
|
assert.doesNotMatch(s, /deploys nothing/);
|
|
81
81
|
assert.doesNotMatch(s, /redeploys from main|on every merge|within minutes/);
|
|
82
|
-
assert.match(s, /cloudbongos[.]com gets a merge only when a published core release carries it and its pin moves
|
|
82
|
+
assert.match(s, /cloudbongos[.]com gets a merge only when a published core release carries it and its pin moves/);
|
|
83
|
+
// task 1005020: the hub's pin is NOT owner-gated for a patch release — its patch-channel
|
|
84
|
+
// subscription moved cloudbongos.com to core 1.24.12 with no person (task 1005015).
|
|
85
|
+
assert.doesNotMatch(s, /owner-gated[)]/, 'a patch move on the hub needs no person');
|
|
86
|
+
assert.match(s, /its patch-channel subscription moves that pin on its own/);
|
|
83
87
|
assert.match(s, /hosted projects: only when a published core release carries the change and their pin moves/);
|
|
84
88
|
assert.match(s, /Never call work live until that instance's \/version shows it/);
|
|
85
89
|
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Pure: every input is injected, so no env mutation / disk dependence.
|
|
3
3
|
import assert from 'node:assert/strict';
|
|
4
4
|
import { test } from 'node:test';
|
|
5
|
+
import fs from 'node:fs';
|
|
5
6
|
import os from 'node:os';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import ic from '../src/instance-config.js';
|
|
@@ -108,7 +109,37 @@ test('resolveEnv: ONLY the old spelling set — resolves, and warns naming old,
|
|
|
108
109
|
assert.equal(lines.length, 1);
|
|
109
110
|
assert.match(lines[0], /CLOUDBONGOS_C3A_RENDER_PROBE/, 'names the old spelling');
|
|
110
111
|
assert.match(lines[0], /BONGOS_C3A_RENDER_PROBE/, 'names the new spelling');
|
|
111
|
-
|
|
112
|
+
// Task 1005042 - legacy env warning: the line used to promise "stops being read
|
|
113
|
+
// in core 1.21" long after 1.21 passed with the spelling still read. Whatever it
|
|
114
|
+
// says about removal must be TRUE against the version actually running: it may
|
|
115
|
+
// name a future release, never the current one or a past one.
|
|
116
|
+
assert.deepEqual(stalePromisedReleases(lines[0], runningCore()), [],
|
|
117
|
+
`the warning names a removal release that is not in the future: ${lines[0]}`);
|
|
118
|
+
assert.match(lines[0], /still read/i, 'says plainly that the old spelling still works today');
|
|
119
|
+
assert.match(lines[0], /future core release/i, 'says it will go, without inventing a date nobody has committed to');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Every "core X.Y" a line names that is NOT after the running release — i.e. a
|
|
123
|
+
// removal promise that is already broken, since the running core still reads the
|
|
124
|
+
// spelling. Empty = the line is honest about dates.
|
|
125
|
+
function runningCore() {
|
|
126
|
+
const coreSrc = fs.readFileSync(new URL('../src/module-api.js', import.meta.url), 'utf8');
|
|
127
|
+
return coreSrc.match(/^const CORE_VERSION = '(\d+)\.(\d+)\.\d+'/m).slice(1).map(Number);
|
|
128
|
+
}
|
|
129
|
+
function stalePromisedReleases(line, [runMaj, runMin]) {
|
|
130
|
+
return [...line.matchAll(/core (\d+)\.(\d+)/g)]
|
|
131
|
+
.map((m) => [Number(m[1]), Number(m[2])])
|
|
132
|
+
.filter(([maj, min]) => !(maj > runMaj || (maj === runMaj && min > runMin)))
|
|
133
|
+
.map(([maj, min]) => `${maj}.${min}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
test('stalePromisedReleases: catches the passed-release warning task 1005042 fixed, accepts a future one', () => {
|
|
137
|
+
const old = '[bongos] deprecated env CLOUDBONGOS_X — rename it to BONGOS_X. The legacy spelling stops being read in core 1.21.';
|
|
138
|
+
assert.deepEqual(stalePromisedReleases(old, [1, 24]), ['1.21'], 'the original line names a release already passed');
|
|
139
|
+
assert.deepEqual(stalePromisedReleases('stops being read in core 1.24.', [1, 24]), ['1.24'], 'the running release is not a future one');
|
|
140
|
+
assert.deepEqual(stalePromisedReleases('stops being read in core 1.25.', [1, 24]), [], 'a genuinely future release is honest');
|
|
141
|
+
assert.deepEqual(stalePromisedReleases('stops being read in core 2.0.', [1, 24]), [], 'a future major is honest');
|
|
142
|
+
assert.ok(runningCore()[0] >= 1, 'CORE_VERSION is readable from src/module-api.js');
|
|
112
143
|
});
|
|
113
144
|
|
|
114
145
|
test('resolveEnv: ONLY the new spelling set — resolves, and is SILENT', () => {
|
package/tests/shell_hooks.mjs
CHANGED
|
@@ -165,6 +165,13 @@ t('a missing hook is visible (exit 1, one line) and never blocks; the others sti
|
|
|
165
165
|
assert.match(r.stderr, /no-such-hook\.js/);
|
|
166
166
|
const b = together(['no-such-hook.js', 'git-guard.js'], shell('Bash', 'git stash push -m x'));
|
|
167
167
|
assert.equal(b.status, 2, 'a real block still blocks beside a missing hook');
|
|
168
|
+
assert.match(b.stderr, /no-such-hook\.js/, 'the missing-hook line survives the block (task 1004949)');
|
|
169
|
+
assert.match(b.stderr, /git-guard: blocked/, 'and the block reason is still there');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
t('merge: a code-1 diagnostic is joined into the block stderr, in roster order (task 1004949)', () => {
|
|
173
|
+
const m = dispatcher.merge([{ code: 1, stderr: 'missing\n' }, { code: 2, stderr: 'blocked\n' }, { code: 0, stdout: '' }]);
|
|
174
|
+
assert.deepEqual(m, { code: 2, stdout: '', stderr: 'missing\nblocked\n' });
|
|
168
175
|
});
|
|
169
176
|
|
|
170
177
|
t('only plain sibling names load — no paths, not itself', () => {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// exited 2. The `require.main === module` guard is what makes the transform testable.
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
10
10
|
import { test } from 'node:test';
|
|
11
|
-
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
12
12
|
import mirror from '../scripts/status-mirror-sync.js';
|
|
13
13
|
|
|
14
14
|
const { buildMirrorIndex, injectOnce, brandTokens } = mirror;
|
|
@@ -120,3 +120,24 @@ test('the publisher source hardcodes no host identity (the ratchet this slice in
|
|
|
120
120
|
const hit = stripped.match(/example|example-owner|example world/i);
|
|
121
121
|
assert.equal(hit, null, `scripts/status-mirror-sync.js hardcodes the host identity "${hit && hit[0]}" — resolve it from branding() instead`);
|
|
122
122
|
});
|
|
123
|
+
|
|
124
|
+
test('no doc claims a sync workflow runs the mirror publish while none exists (task 1005131)', () => {
|
|
125
|
+
// The publish is run by hand: nothing in .github/workflows/ calls this script. A doc
|
|
126
|
+
// saying "synced by sync-status-mirror.yml" (or calling the sync CI-driven) sends the
|
|
127
|
+
// reader waiting for an automatic update that never comes, so the live page goes stale.
|
|
128
|
+
const wfDir = new URL('../.github/workflows/', import.meta.url);
|
|
129
|
+
const workflows = readdirSync(wfDir).map((f) => readFileSync(new URL(f, wfDir), 'utf8'));
|
|
130
|
+
const automated = workflows.some((w) => /status-mirror-sync\.js/.test(w));
|
|
131
|
+
if (automated) return; // a real workflow now runs it — the claim would be true
|
|
132
|
+
const docs = [
|
|
133
|
+
'scripts/status-mirror-sync.js',
|
|
134
|
+
'docs/adr/0029-status-page-outage-resilience.md',
|
|
135
|
+
'docs/architecture.md',
|
|
136
|
+
'docs/recipes/ops-gotchas.md',
|
|
137
|
+
];
|
|
138
|
+
for (const rel of docs) {
|
|
139
|
+
const text = readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8');
|
|
140
|
+
const hit = text.match(/synced by[^\n]{0,40}sync-status-mirror\.yml|(?<!NOT )CI-driven/i);
|
|
141
|
+
assert.equal(hit, null, `${rel} claims an automatic mirror sync ("${hit && hit[0]}"), but no workflow runs scripts/status-mirror-sync.js`);
|
|
142
|
+
}
|
|
143
|
+
});
|