@bongos/core 1.19.617 → 1.19.619
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 +64 -24
- package/.claude/skills/backlog-review/SKILL.md +175 -0
- package/docs/adr/0268-the-constitution-comes-from-two-roots.md +132 -0
- package/docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md +104 -0
- package/docs/adr/README.md +2 -0
- package/docs/copy-inventory.md +36 -33
- package/docs/copy-registry.json +61 -34
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/government/board.js +58 -0
- package/modules/government/config.js +34 -9
- package/modules/hall-ui/public/government.js +54 -0
- package/modules/hall-ui/public/oversight.css +25 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/backlog-review.js +399 -0
- package/scripts/gds/build-cli-package.js +1 -1
- package/scripts/gds/cli-lib.js +110 -0
- package/scripts/gds/login.js +40 -2
- package/src/module-api.js +27 -1
- package/tests/backlog_review.mjs +183 -0
- package/tests/cli_sessions.mjs +168 -0
- package/tests/government_config_roots.mjs +159 -0
- package/tests/government_constitution_divergence.mjs +123 -0
- package/tests/module_api.mjs +1 -0
package/.bongos-core.json
CHANGED
|
@@ -2,23 +2,28 @@
|
|
|
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.619",
|
|
6
|
+
"core_contract": "1.19.619",
|
|
7
|
+
"source_commit": "b3a494e84958a264492cf55b3525d70680f7bbda",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-09T06:39:30.103Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 463,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2086,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2573,
|
|
20
|
+
"tree_sha256": "f0ad9c8d1e70405ab669163d2d2153cd7f6ae97a699036bb8e4278f1e3a435d5",
|
|
21
21
|
"files": [
|
|
22
|
+
{
|
|
23
|
+
"path": ".claude/skills/backlog-review/SKILL.md",
|
|
24
|
+
"mode": "0000644",
|
|
25
|
+
"sha256": "ce45a242487fa6d10794280958fea47f8a23e2618551f629abe38054d467a59c"
|
|
26
|
+
},
|
|
22
27
|
{
|
|
23
28
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
24
29
|
"mode": "0000644",
|
|
@@ -1854,10 +1859,20 @@
|
|
|
1854
1859
|
"mode": "0000644",
|
|
1855
1860
|
"sha256": "32a1e0c4fe9423008caf491e5d3594aa4a07874a46a047a86e5eb321baef773a"
|
|
1856
1861
|
},
|
|
1862
|
+
{
|
|
1863
|
+
"path": "docs/adr/0268-the-constitution-comes-from-two-roots.md",
|
|
1864
|
+
"mode": "0000644",
|
|
1865
|
+
"sha256": "cec61252a02d5da416c77088dbf2adf99c138cb10b654fa9fef949a0848a7964"
|
|
1866
|
+
},
|
|
1867
|
+
{
|
|
1868
|
+
"path": "docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md",
|
|
1869
|
+
"mode": "0000644",
|
|
1870
|
+
"sha256": "cc44045ea59f76dabc62fe43993ffab6004add88c7f6142bfe9215f7f2c33c99"
|
|
1871
|
+
},
|
|
1857
1872
|
{
|
|
1858
1873
|
"path": "docs/adr/README.md",
|
|
1859
1874
|
"mode": "0000644",
|
|
1860
|
-
"sha256": "
|
|
1875
|
+
"sha256": "d660b4bc0bc3306f5fcf583118ca9149ba3deafe82dfe3894fe0b1c11b60cc2f"
|
|
1861
1876
|
},
|
|
1862
1877
|
{
|
|
1863
1878
|
"path": "docs/api-reference.md",
|
|
@@ -1892,12 +1907,12 @@
|
|
|
1892
1907
|
{
|
|
1893
1908
|
"path": "docs/copy-inventory.md",
|
|
1894
1909
|
"mode": "0000644",
|
|
1895
|
-
"sha256": "
|
|
1910
|
+
"sha256": "0e4c54e6bd6d76fdd7853b19c343b747fabfc2f59ee8baab74cbaebc6064aa1e"
|
|
1896
1911
|
},
|
|
1897
1912
|
{
|
|
1898
1913
|
"path": "docs/copy-registry.json",
|
|
1899
1914
|
"mode": "0000644",
|
|
1900
|
-
"sha256": "
|
|
1915
|
+
"sha256": "4357446e8f95c430791743a43f893140d862ab7dd5453f6a42791b194956d61c"
|
|
1901
1916
|
},
|
|
1902
1917
|
{
|
|
1903
1918
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2732,7 +2747,7 @@
|
|
|
2732
2747
|
{
|
|
2733
2748
|
"path": "docs/file-map.md",
|
|
2734
2749
|
"mode": "0000644",
|
|
2735
|
-
"sha256": "
|
|
2750
|
+
"sha256": "0cec3b144d8cca893b49c1d08bdacf37220dc1b670fd771656dda659bae16edc"
|
|
2736
2751
|
},
|
|
2737
2752
|
{
|
|
2738
2753
|
"path": "docs/handoff-template.md",
|
|
@@ -2742,7 +2757,7 @@
|
|
|
2742
2757
|
{
|
|
2743
2758
|
"path": "docs/module-api-changelog.md",
|
|
2744
2759
|
"mode": "0000644",
|
|
2745
|
-
"sha256": "
|
|
2760
|
+
"sha256": "1fe02fc79e2952efb61450bbd45e6b09e41e8cf73453c6e0a2fbbf2944c1cdab"
|
|
2746
2761
|
},
|
|
2747
2762
|
{
|
|
2748
2763
|
"path": "docs/modules-contract.md",
|
|
@@ -4397,7 +4412,7 @@
|
|
|
4397
4412
|
{
|
|
4398
4413
|
"path": "modules/government/board.js",
|
|
4399
4414
|
"mode": "0000644",
|
|
4400
|
-
"sha256": "
|
|
4415
|
+
"sha256": "68bf53ab31ec3f21359665eea42127f1691b8dc17b59100c65cc1f56a04ad6a7"
|
|
4401
4416
|
},
|
|
4402
4417
|
{
|
|
4403
4418
|
"path": "modules/government/catalog.js",
|
|
@@ -4407,7 +4422,7 @@
|
|
|
4407
4422
|
{
|
|
4408
4423
|
"path": "modules/government/config.js",
|
|
4409
4424
|
"mode": "0000644",
|
|
4410
|
-
"sha256": "
|
|
4425
|
+
"sha256": "8edccad7dc69a95503d9e0d1de7795fd6082c8122e0695d1f018f49b37051fed"
|
|
4411
4426
|
},
|
|
4412
4427
|
{
|
|
4413
4428
|
"path": "modules/government/db.js",
|
|
@@ -4922,7 +4937,7 @@
|
|
|
4922
4937
|
{
|
|
4923
4938
|
"path": "modules/hall-ui/public/government.js",
|
|
4924
4939
|
"mode": "0000644",
|
|
4925
|
-
"sha256": "
|
|
4940
|
+
"sha256": "6333b05761e5bf3215fe3c19ae98b25ebdac8cf0761df28fbc4933a5317ce5fa"
|
|
4926
4941
|
},
|
|
4927
4942
|
{
|
|
4928
4943
|
"path": "modules/hall-ui/public/government.states.json",
|
|
@@ -5052,7 +5067,7 @@
|
|
|
5052
5067
|
{
|
|
5053
5068
|
"path": "modules/hall-ui/public/oversight.css",
|
|
5054
5069
|
"mode": "0000644",
|
|
5055
|
-
"sha256": "
|
|
5070
|
+
"sha256": "a199baf1c11bdbe6d30931253956e0c9a9709ae3f880af77b2a6737212fe0984"
|
|
5056
5071
|
},
|
|
5057
5072
|
{
|
|
5058
5073
|
"path": "modules/hall-ui/public/pair.css",
|
|
@@ -7642,12 +7657,12 @@
|
|
|
7642
7657
|
{
|
|
7643
7658
|
"path": "package-lock.json",
|
|
7644
7659
|
"mode": "0000644",
|
|
7645
|
-
"sha256": "
|
|
7660
|
+
"sha256": "41aca5e97b1cb72e389656c14ddd1bb35a3d5fdce8145f05e294c3fa3f52dc15"
|
|
7646
7661
|
},
|
|
7647
7662
|
{
|
|
7648
7663
|
"path": "package.json",
|
|
7649
7664
|
"mode": "0000644",
|
|
7650
|
-
"sha256": "
|
|
7665
|
+
"sha256": "2084b4c59fd9b3857bb83b5a4198d43c308f0180edc0e21a698e4285809323fd"
|
|
7651
7666
|
},
|
|
7652
7667
|
{
|
|
7653
7668
|
"path": "public-docs/index.html",
|
|
@@ -7794,6 +7809,11 @@
|
|
|
7794
7809
|
"mode": "0000644",
|
|
7795
7810
|
"sha256": "a9f1064fbb683297e775c455ef65249cf809018a38a5aa2f975a62cbba16cd9c"
|
|
7796
7811
|
},
|
|
7812
|
+
{
|
|
7813
|
+
"path": "scripts/gds/backlog-review.js",
|
|
7814
|
+
"mode": "0000644",
|
|
7815
|
+
"sha256": "4646bd6da8be2d2c40c1063bba47f5fdce0f2f63143e26fc16e36ea941adbc88"
|
|
7816
|
+
},
|
|
7797
7817
|
{
|
|
7798
7818
|
"path": "scripts/gds/backup.js",
|
|
7799
7819
|
"mode": "0000644",
|
|
@@ -7847,7 +7867,7 @@
|
|
|
7847
7867
|
{
|
|
7848
7868
|
"path": "scripts/gds/build-cli-package.js",
|
|
7849
7869
|
"mode": "0000644",
|
|
7850
|
-
"sha256": "
|
|
7870
|
+
"sha256": "de1cdede17761775722509b1e1edb4bb43fbbf336450556f22119b18b93e43d3"
|
|
7851
7871
|
},
|
|
7852
7872
|
{
|
|
7853
7873
|
"path": "scripts/gds/bump-version.js",
|
|
@@ -7907,7 +7927,7 @@
|
|
|
7907
7927
|
{
|
|
7908
7928
|
"path": "scripts/gds/cli-lib.js",
|
|
7909
7929
|
"mode": "0000644",
|
|
7910
|
-
"sha256": "
|
|
7930
|
+
"sha256": "eaa4c3a172009d5ce825e528f5077a480e034658501a7d0c532a7807886beb9e"
|
|
7911
7931
|
},
|
|
7912
7932
|
{
|
|
7913
7933
|
"path": "scripts/gds/client-baseurl-guard.js",
|
|
@@ -8287,7 +8307,7 @@
|
|
|
8287
8307
|
{
|
|
8288
8308
|
"path": "scripts/gds/login.js",
|
|
8289
8309
|
"mode": "0000644",
|
|
8290
|
-
"sha256": "
|
|
8310
|
+
"sha256": "bdc7fcc0876a1185fb15339740793944c755d716d998bb68533ab3a721d39ab2"
|
|
8291
8311
|
},
|
|
8292
8312
|
{
|
|
8293
8313
|
"path": "scripts/gds/main-audit.js",
|
|
@@ -9372,7 +9392,7 @@
|
|
|
9372
9392
|
{
|
|
9373
9393
|
"path": "src/module-api.js",
|
|
9374
9394
|
"mode": "0000644",
|
|
9375
|
-
"sha256": "
|
|
9395
|
+
"sha256": "983d456cff4042137990a5baacb002919c3d213a33f663d6e8fcc7e9f5945ae6"
|
|
9376
9396
|
},
|
|
9377
9397
|
{
|
|
9378
9398
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9679,6 +9699,11 @@
|
|
|
9679
9699
|
"mode": "0000644",
|
|
9680
9700
|
"sha256": "25f45e6a0f79e6e6299b062dd89306f898fc1ed8cefbf4fa16b76c9c511f0548"
|
|
9681
9701
|
},
|
|
9702
|
+
{
|
|
9703
|
+
"path": "tests/backlog_review.mjs",
|
|
9704
|
+
"mode": "0000644",
|
|
9705
|
+
"sha256": "788f3b3faba5f0568a251d3551c8776e2f383d59c8c2ed0728ef4c9af9a208c7"
|
|
9706
|
+
},
|
|
9682
9707
|
{
|
|
9683
9708
|
"path": "tests/backup_cli.mjs",
|
|
9684
9709
|
"mode": "0000644",
|
|
@@ -9974,6 +9999,11 @@
|
|
|
9974
9999
|
"mode": "0000644",
|
|
9975
10000
|
"sha256": "a57516ed9e904cf1c86fe99688415f834dcae1a19f01b22e986f199fa5473258"
|
|
9976
10001
|
},
|
|
10002
|
+
{
|
|
10003
|
+
"path": "tests/cli_sessions.mjs",
|
|
10004
|
+
"mode": "0000644",
|
|
10005
|
+
"sha256": "6ec406bf2bcbae261ab37ecaf820cfbe196c11007b0d8d3ee47cf21b6a3919ca"
|
|
10006
|
+
},
|
|
9977
10007
|
{
|
|
9978
10008
|
"path": "tests/cli_surface.mjs",
|
|
9979
10009
|
"mode": "0000644",
|
|
@@ -10689,6 +10719,16 @@
|
|
|
10689
10719
|
"mode": "0000644",
|
|
10690
10720
|
"sha256": "a8e575d151aca92789ffa9d3a49f77aa2579fa285374b2c1f5a65b12ac82163d"
|
|
10691
10721
|
},
|
|
10722
|
+
{
|
|
10723
|
+
"path": "tests/government_config_roots.mjs",
|
|
10724
|
+
"mode": "0000644",
|
|
10725
|
+
"sha256": "d3060b7302f17a1478588ee333ba9e2f4deea23d5896363b9bd42e30edb12c88"
|
|
10726
|
+
},
|
|
10727
|
+
{
|
|
10728
|
+
"path": "tests/government_constitution_divergence.mjs",
|
|
10729
|
+
"mode": "0000644",
|
|
10730
|
+
"sha256": "280bc5906ac8075495cd3b1fe10e4082291cf4497b166f6acbdec291a8fd6698"
|
|
10731
|
+
},
|
|
10692
10732
|
{
|
|
10693
10733
|
"path": "tests/government_constitution_view.mjs",
|
|
10694
10734
|
"mode": "0000644",
|
|
@@ -11442,7 +11482,7 @@
|
|
|
11442
11482
|
{
|
|
11443
11483
|
"path": "tests/module_api.mjs",
|
|
11444
11484
|
"mode": "0000644",
|
|
11445
|
-
"sha256": "
|
|
11485
|
+
"sha256": "cc6fb31e06f0b2accf46f1595f9548492698c88ff56c4d9f4f43c86fce6816a3"
|
|
11446
11486
|
},
|
|
11447
11487
|
{
|
|
11448
11488
|
"path": "tests/module_api_lazy.mjs",
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backlog-review
|
|
3
|
+
description: >-
|
|
4
|
+
Daily walk of status='backlog' — the pre-workable state a human must say go on.
|
|
5
|
+
Separates rows waiting on a PERSON (walked: promote, kill, water) from rows
|
|
6
|
+
waiting on a TRIGGER, and surfaces rows stranded behind an abandoned dependency.
|
|
7
|
+
Triggers on "/backlog-review", "review the backlog", "what needs a nod", "walk
|
|
8
|
+
the backlog", "what's waiting on me to approve". Metic+ only.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
You are running the backlog-review session. Tasks at `status='backlog'` get
|
|
12
|
+
walked and verdicted, so work nobody has said go on doesn't sit un-nodded
|
|
13
|
+
forever.
|
|
14
|
+
|
|
15
|
+
## Why this exists (task 1003746)
|
|
16
|
+
|
|
17
|
+
`backlog` is the **pre-workable** state: a task lands there when the system will
|
|
18
|
+
not put it in the claimable queue without a human saying go. `routing.js` says it
|
|
19
|
+
plainly — *"'backlog' means a human still says go"* — and [ADR 0234](../../../docs/adr/<redacted>.md) §2
|
|
20
|
+
records why: a feature/cleanup/refactor filing *"reshape[s] what gets built and a
|
|
21
|
+
filer should not be able to set the queue's agenda alone."*
|
|
22
|
+
|
|
23
|
+
Every **other** queue in the methodology already has a walkable cadence:
|
|
24
|
+
`/idea-triage` for `idea_inbox`, `/blocker-review` for blockers, `/bug-triage`
|
|
25
|
+
for `kind=bug`, `/goal-review` for criteria. The backlog had none. Its only pull
|
|
26
|
+
surfaces were:
|
|
27
|
+
|
|
28
|
+
- the **nod queue** (`GET /inbox/awaiting-nod`), scoped `WHERE origin='route'` —
|
|
29
|
+
so it sees only filings that arrived through the idea inbox, and
|
|
30
|
+
- the **30-day rot timer** (`GET /inbox/rotting`, [ADR 0232](../../../docs/adr/<redacted>.md)).
|
|
31
|
+
|
|
32
|
+
Neither is a review of the backlog. This skill is that missing walk.
|
|
33
|
+
|
|
34
|
+
## Rank gate — Metic+ only
|
|
35
|
+
|
|
36
|
+
**Gated to Metic and above (Metic, Archon); not Xenos or Thetes.** Promoting a
|
|
37
|
+
backlog task puts it in the claimable queue for the whole team and, if its reward
|
|
38
|
+
is unset, prices it — `task.promote` and `task.abandon` are both floor-`metic`
|
|
39
|
+
permissions (`modules/government/catalog.js`). A sub-Metic can still **file**
|
|
40
|
+
work (`POST /tasks`, `capture.js`, Discord `#bugs`) — that path stays open; they
|
|
41
|
+
just can't say go on it.
|
|
42
|
+
|
|
43
|
+
**Enforce the gate at the top of the session, before reading the queue:**
|
|
44
|
+
|
|
45
|
+
1. Call `GET /api/gds/me` and read `builder.rank`. Compare case-insensitively.
|
|
46
|
+
2. **If `rank` lowercases to `xenos` or `thetes`** → stop immediately. Tell the
|
|
47
|
+
builder: *"Backlog review is a Metic+ operation — promoting a task puts it in
|
|
48
|
+
the claimable queue for everyone and sets its price. You can still file work;
|
|
49
|
+
ask an Archon to promote you if you need to nod it through."* Do not list the
|
|
50
|
+
queue, do not propose verdicts.
|
|
51
|
+
3. **If `rank` lowercases to `metic` or `archon`** → proceed below.
|
|
52
|
+
4. **If `rank` is absent** (pre-rank deployment) → treat the session as
|
|
53
|
+
available, the same pre-rank passthrough `/idea-triage` uses.
|
|
54
|
+
|
|
55
|
+
> Belt-and-braces: the server already rejects `POST /tasks/:id/promote` and
|
|
56
|
+
> `/abandon` with `403 permission_forbidden` for a sub-Metic caller. This check
|
|
57
|
+
> stops one early instead of after a wall of 403s. Markdown never grants
|
|
58
|
+
> authority ([`docs/canonical-permissions.md`](../../../docs/canonical-permissions.md), ADR 0016).
|
|
59
|
+
|
|
60
|
+
## The four buckets — and why only two get walked
|
|
61
|
+
|
|
62
|
+
**This is the whole point of the skill.** One read partitions the backlog into
|
|
63
|
+
four populations, and conflating them is the trap: *a row waiting on a TRIGGER is
|
|
64
|
+
not a row waiting on a PERSON.* Asking a human to verdict the former burns the
|
|
65
|
+
cadence on rows it cannot move.
|
|
66
|
+
|
|
67
|
+
| Bucket | What it is | Walked? |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| **stranded** | dep-gated, but a dependency is `abandoned` — that edge can never satisfy, so the auto-promote trigger will **never** fire | **first** — it only gets worse if left |
|
|
70
|
+
| **awaiting a nod** | no unsatisfied dependency; nothing gates it but a human | **yes — the review subject** |
|
|
71
|
+
| **waiting on a dependency** | an unsatisfied but still-live dep; shipping it auto-promotes the row (migration 163) | no — counted only |
|
|
72
|
+
| **spikes** | `kind='spike'` lives at backlog **by convention** (migration 020: *"Spikes stay in backlog by convention"*) | no — counted only |
|
|
73
|
+
|
|
74
|
+
A promote on a healthy dep-gated row would race the trigger and put work in the
|
|
75
|
+
queue whose prerequisites aren't done. Don't.
|
|
76
|
+
|
|
77
|
+
## The verbs are status-derived — do NOT offer prune
|
|
78
|
+
|
|
79
|
+
The rot card publishes four task verbs (prioritise / prune / kill / water), but
|
|
80
|
+
two are **status-conditional at the route**:
|
|
81
|
+
|
|
82
|
+
- `POST /tasks/:id/promote` accepts only `backlog | blocked | abandoned`
|
|
83
|
+
- `POST /tasks/:id/demote` accepts only `ready` (`routes/tasks.js`: `if (task.status !== 'ready') return res.fail('cannot_demote', 409)`)
|
|
84
|
+
|
|
85
|
+
So on a **backlog** row, "prune" is a guaranteed `409 cannot_demote` — it is
|
|
86
|
+
already at the bottom. This walk offers:
|
|
87
|
+
|
|
88
|
+
| Verb | Route | Meaning |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| **promote** | `POST /tasks/:id/promote` | the nod — backlog → ready, claimable now |
|
|
91
|
+
| **kill** | `POST /tasks/:id/abandon` | it is not going to happen (restorable — ADR 0152) |
|
|
92
|
+
| **water** | `POST /tasks/:id/water` | leave it; reset the rot clock. Changes no status, destroys nothing — its route is `requireBuilder` plus an ownership-or-atom check (creator, or the see-all-rot atom), which every Metic+ caller here holds per ADR 0157 |
|
|
93
|
+
| **skip** | — | no write; it surfaces again next review |
|
|
94
|
+
|
|
95
|
+
Every one is an **existing** route, so this skill adds no new authority — the
|
|
96
|
+
[ADR 0232](../../../docs/adr/<redacted>.md)
|
|
97
|
+
precedent (*"a verb that reuses a route also reuses its permission"*).
|
|
98
|
+
|
|
99
|
+
## How to use
|
|
100
|
+
|
|
101
|
+
**Step 1 — run the walk.**
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
node scripts/gds/backlog-review.js
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
It reads `GET /tasks?status=backlog&include=deps&limit=1000`, prints the four
|
|
108
|
+
bucket counts, then walks stranded rows first and the nod queue after — each
|
|
109
|
+
**grouped goal-by-goal** under a header naming the goal. Flags: `--limit N`
|
|
110
|
+
(default 20 per sitting), `--all`, `--goal ID`.
|
|
111
|
+
|
|
112
|
+
**Why goal-by-goal matters to how you prompt.** The nod is mostly a *scope*
|
|
113
|
+
judgment — "does this goal want this work next" — and that is one decision per
|
|
114
|
+
goal, not per row. So read the goal header, then offer its rows together; you
|
|
115
|
+
will often give the same verdict to three in a row. Within a goal the order is
|
|
116
|
+
oldest-first, and goals themselves are ordered by their own oldest waiting row,
|
|
117
|
+
so the longest-ignored goal comes up first and nothing hides behind a busy one.
|
|
118
|
+
`--goal ID` narrows to a single goal; grouping is what organises the *whole*
|
|
119
|
+
queue.
|
|
120
|
+
|
|
121
|
+
> The `include=deps` parameter is load-bearing — it is what returns `blocked_by[]`
|
|
122
|
+
> with a per-edge `satisfied` flag, and without it every row looks like it's
|
|
123
|
+
> waiting on a person. `limit` is capped at 1000 by the route.
|
|
124
|
+
|
|
125
|
+
**Step 2 — verdict each row.** For each, the walk shows title, description, goal,
|
|
126
|
+
kind, estimate, reward, and filing date, then asks:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
[p]romote (make claimable) / [k]ill / [w]ater (leave it, reset the clock) / [s]kip / [q]uit
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Frame the question around the **outcome**, not the mechanics — the prompter is
|
|
133
|
+
often the owner, who is not a software engineer:
|
|
134
|
+
|
|
135
|
+
> #1003678 "Goals have no intentionality record" — filed 2026-09-07, ~2h,
|
|
136
|
+
> goal 1000089. Say go, drop it, or leave it for now?
|
|
137
|
+
|
|
138
|
+
**Step 3 — watch the price on a promote.** If `credits_reward` is 0/NULL, promote
|
|
139
|
+
**auto-assigns** a capped suggestion rather than refusing (ADR 0096 + the task
|
|
140
|
+
1673 amendment). The walk echoes `assigned_credits_reward` — read it out, because
|
|
141
|
+
that number is what the task now pays.
|
|
142
|
+
|
|
143
|
+
**Step 4 — handle a stranded row deliberately.** A stranded row prints its dead
|
|
144
|
+
edges and the two ways out:
|
|
145
|
+
|
|
146
|
+
- **drop the dead edge** — `DELETE /api/bongos/dependencies` with
|
|
147
|
+
`{"from_kind":"task","from_id":<this>,"to_kind":"task","to_id":<dead>}`, then
|
|
148
|
+
the row becomes a normal nod decision; or
|
|
149
|
+
- **restore the dependency** — `POST /api/bongos/tasks/<dead>/promote` returns an
|
|
150
|
+
abandoned task to backlog (ADR 0152).
|
|
151
|
+
|
|
152
|
+
Never just promote a stranded row past its dead edge without one of those — the
|
|
153
|
+
dependency was declared for a reason, and dropping it silently is the decision
|
|
154
|
+
being made by accident.
|
|
155
|
+
|
|
156
|
+
**Step 5 — close out.** The walk prints a summary and how many rows still await a
|
|
157
|
+
human. Note the counts in the session log. If the same rows get skipped three
|
|
158
|
+
reviews running, they are telling you something: either kill them, or the goal
|
|
159
|
+
they sit in has gone quiet and belongs in `/goal-review`.
|
|
160
|
+
|
|
161
|
+
## Cadence
|
|
162
|
+
|
|
163
|
+
Run it **once a day**, like `/idea-triage` and `/blocker-review` — it is a manual
|
|
164
|
+
slash command, not a scheduled routine. Deferring a row is fine; the cadence is
|
|
165
|
+
the discipline. If a day passes without it, the queue quietly grows.
|
|
166
|
+
|
|
167
|
+
## Non-goals
|
|
168
|
+
|
|
169
|
+
- **Not a re-prioritiser.** Reordering what's already `ready` is
|
|
170
|
+
`/priority-session`. This skill only decides whether a row becomes claimable.
|
|
171
|
+
- **Not dependency surgery.** Editing the dep graph beyond dropping one dead edge
|
|
172
|
+
belongs in its own task — the graph is load-bearing (ADR 0015).
|
|
173
|
+
- **Not a duplicate-folder.** Merging near-duplicate reports is `/bug-triage`.
|
|
174
|
+
- **Not a promote-everything button.** A backlog that empties every day means the
|
|
175
|
+
nod has stopped being a decision, which is the gate ADR 0234 §2 exists to keep.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# 0268 — The constitution comes from two roots, and a decision that did not take must say so
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-09
|
|
5
|
+
- **Tasks:** [#1003739](https://cloudbongos.com/builders#/task/1003739) (goal [#1000111](https://cloudbongos.com/builders#/goal/1000111) — Government). Found while shipping [#1003733](https://cloudbongos.com/builders#/task/1003733) ([ADR 0267](<redacted>.md)).
|
|
6
|
+
- **Builds on:** [ADR 0108](<redacted>.md) §1 (the two configurable roots), [ADR 0175](<redacted>.md) §7 (a passed amendment becomes the live constitution), [ADR 0083](<redacted>.md) (a module reaches core only through the doorway).
|
|
7
|
+
|
|
8
|
+
## 1. What happened
|
|
9
|
+
|
|
10
|
+
On 2026-08-25 cloudbongos.com's board ratified a constitutional amendment:
|
|
11
|
+
`rank:metic+` · `consent` · a 1440-minute sitting. It passed, it was recorded,
|
|
12
|
+
the proposer was paid the ratification karma — and **the constitution never
|
|
13
|
+
changed.** Two weeks later `GET /government/constitution` still answered the
|
|
14
|
+
day-one monarchy, with the passed amendment sitting in its own `history` array
|
|
15
|
+
directly beneath the `board` block that contradicted it.
|
|
16
|
+
|
|
17
|
+
Every Board Room sitting in those two weeks was decided under a rule the board
|
|
18
|
+
had voted to replace. It also quietly un-shipped [ADR 0191](<redacted>.md)
|
|
19
|
+
(majority) and would have un-shipped ADR 0267 (unanimity) the same way: both
|
|
20
|
+
shipped as code, and neither could reach anybody.
|
|
21
|
+
|
|
22
|
+
Nobody noticed, because nothing was broken enough to notice. No error, no failed
|
|
23
|
+
write, no missing row.
|
|
24
|
+
|
|
25
|
+
## 2. The cause: one root where there are two
|
|
26
|
+
|
|
27
|
+
`modules/government/config.js` resolved both of its files from one place:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
31
|
+
const NEUTRAL_PATH = path.join(ROOT, 'config', 'government.neutral.json');
|
|
32
|
+
const INSTANCE_PATH = path.join(ROOT, 'config', 'government.json');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The comment above it said the two roots coincided *today* and that the instance
|
|
36
|
+
path would route through `resolveInstanceRoot()` when ADR 0108's split landed.
|
|
37
|
+
**The split had landed.** cloudbongos.com is a standalone instance: its systemd
|
|
38
|
+
unit runs `node_modules/@cloudbongos/core`'s server with
|
|
39
|
+
`WorkingDirectory=<instance repo>`, so at runtime `__dirname` is *inside the core
|
|
40
|
+
package*. Which makes:
|
|
41
|
+
|
|
42
|
+
| file | resolved to | correct? |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `government.neutral.json` | `…/node_modules/@cloudbongos/core/config/` | ✅ — it really is core content |
|
|
45
|
+
| `government.json` | `…/node_modules/@cloudbongos/core/config/` | ❌ — the host's is at `<instance>/config/` |
|
|
46
|
+
|
|
47
|
+
So two things were true at once, and each hid the other:
|
|
48
|
+
|
|
49
|
+
1. **The host's own constitution was never read.** An instance authoring
|
|
50
|
+
`config/government.json` — the documented way to set one — was ignored.
|
|
51
|
+
2. **`applyBoardAmendment` wrote the ratified amendment into `node_modules`,**
|
|
52
|
+
where it worked until the next core upgrade deleted it. The nightly upgrade
|
|
53
|
+
sweep (ADR 0161 cuts a release per merge) did exactly that, within a day.
|
|
54
|
+
|
|
55
|
+
**Nothing threw, and nothing could have.** Both paths exist, both are writable,
|
|
56
|
+
and an absent instance pack is a legal state — a vanilla instance has none — so
|
|
57
|
+
the read fell through to the neutral monarchy precisely as designed. This class
|
|
58
|
+
of bug has no error to catch. `src/branding.js` had it right the whole time and
|
|
59
|
+
was three files away: neutral from `resolveCoreRoot()`, instance from
|
|
60
|
+
`resolveInstanceRoot()`.
|
|
61
|
+
|
|
62
|
+
## 3. The fix, and the doorway it needed
|
|
63
|
+
|
|
64
|
+
`NEUTRAL_PATH` now resolves from the core root and `INSTANCE_PATH` from the
|
|
65
|
+
instance root, reached through `src/module-api` — because ADR 0083 forbids a
|
|
66
|
+
module requiring a core internal, and until now the doorway exposed neither
|
|
67
|
+
resolver. That absence is *why* the module re-derived a root from `__dirname`:
|
|
68
|
+
the only tool it had was the wrong one. Both are now exported, with the failure
|
|
69
|
+
above written into the export comment.
|
|
70
|
+
|
|
71
|
+
Two smaller decisions travel with it:
|
|
72
|
+
|
|
73
|
+
- **Resolved at load, not per call.** `resolveInstanceRoot()` falls back to
|
|
74
|
+
`process.cwd()`, so resolving lazily would let a `chdir` mid-process move the
|
|
75
|
+
constitution — a worse failure than the one being fixed.
|
|
76
|
+
- **`applyBoardAmendment` creates the directory.** The write now targets a
|
|
77
|
+
directory the core does not own, and an instance that has never been branded
|
|
78
|
+
has no `config/` of its own. A ratified amendment lost to `ENOENT` is the same
|
|
79
|
+
failure in a different coat.
|
|
80
|
+
|
|
81
|
+
The single-checkout case is byte-identical, and a test asserts that rather than
|
|
82
|
+
the prose claiming it.
|
|
83
|
+
|
|
84
|
+
## 4. The detector, because the fix alone is not the lesson
|
|
85
|
+
|
|
86
|
+
Fixing the path fixes *this* cause. The thing worth keeping is that **the API
|
|
87
|
+
held both facts in one payload for two weeks and never compared them.**
|
|
88
|
+
|
|
89
|
+
`constitutionView` now carries a `divergence` block: the newest **passed**
|
|
90
|
+
amendment's proposal, sanitized the way an apply would have written it, compared
|
|
91
|
+
field by field against the constitution actually in force. Null when they agree.
|
|
92
|
+
The hall renders it above the dials — in words, not config keys — as *"the board
|
|
93
|
+
ratified a change that is not in force."*
|
|
94
|
+
|
|
95
|
+
Three properties, each chosen against a plausible alternative:
|
|
96
|
+
|
|
97
|
+
- **It reports; it never heals.** Re-applying automatically was refused: the
|
|
98
|
+
instance pack is *also* the file a human editing the constitution touches (ADR
|
|
99
|
+
0175 §7), so a silent re-apply would revert a legitimate hand edit with no way
|
|
100
|
+
to tell the two apart — trading a visible wrong constitution for an unstoppable
|
|
101
|
+
one. Naming the divergence is what those two weeks actually lacked.
|
|
102
|
+
- **A returned amendment is never compared.** That is the whole meaning of
|
|
103
|
+
*returned*; comparing against it would fire on every rejected proposal, i.e. on
|
|
104
|
+
the system working.
|
|
105
|
+
- **It compares the SANITIZED proposal.** Comparing the raw one would report a
|
|
106
|
+
phantom divergence on any field the sanitizer legitimately normalises — and a
|
|
107
|
+
warning that can never be cleared is a warning people learn to scroll past.
|
|
108
|
+
|
|
109
|
+
## 5. What this does not do
|
|
110
|
+
|
|
111
|
+
**It does not put the 2026-08-25 amendment back in force.** That is a board act,
|
|
112
|
+
not a deploy: once this ships, cloudbongos.com's Constitution tab will *say* the
|
|
113
|
+
amendment is not in force, and the remedy is to put it back to the board (now
|
|
114
|
+
that a pass will stick) or for an operator to author the instance's own
|
|
115
|
+
`config/government.json`. Choosing between those is the owner's, not this task's.
|
|
116
|
+
|
|
117
|
+
**It fixes one module, and the sweep for siblings was run rather than promised.**
|
|
118
|
+
Every other module deriving a root from `__dirname` was checked, and all but one
|
|
119
|
+
are reading genuine CORE content — `scripts/`, `infra/`, the UI kit — which the
|
|
120
|
+
core root is the *right* answer for. The exception is
|
|
121
|
+
`modules/security/artifact-scan/quarantine.js`, whose "a quarantine site must not
|
|
122
|
+
be inside the repository" refusal resolves to the core package on a standalone
|
|
123
|
+
instance and so does not refuse a site inside the instance repo itself. That is a
|
|
124
|
+
real, narrow weakening of a security guard; it is filed as
|
|
125
|
+
[#1003745](https://cloudbongos.com/builders#/task/1003745) on the Security area
|
|
126
|
+
rather than absorbed here, and the doorway exports this ADR adds are what its fix
|
|
127
|
+
will use.
|
|
128
|
+
|
|
129
|
+
**The rule to carry forward:** a `__dirname`-derived root is correct for reading
|
|
130
|
+
what the core ships and wrong for reading what the host owns, and on a
|
|
131
|
+
single-checkout instance the two are indistinguishable — which is why this kind
|
|
132
|
+
of bug is always found in production and never in a test that runs at home.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# ADR 0269 — The CLI's session store is host-keyed at a fixed anchor; the per-brand file stays the active pointer
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted
|
|
4
|
+
- **Date:** 2026-09-09
|
|
5
|
+
- **Task:** [task 1003741](https://cloudbongos.com/builders#/task/1003741) (BONGOS-V2, goal 1000090 — *Working area 4, Bongos Core distribution*)
|
|
6
|
+
- **Deciders:** Claude, under the Archon's standing scope
|
|
7
|
+
- **Related:** [ADR 0258](<redacted>.md) (the public CLI, which is where this bites) · [ADR 0108](<redacted>.md) (each instance its own checkout) · [ADR 0083](<redacted>.md) (why the store directory may not be called `sessions`)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
The CLI resolved its session path from the **branding pack**, which is read out of whatever
|
|
14
|
+
checkout the process happens to be standing in. That gave it exactly **one session slot**, and the
|
|
15
|
+
slot moved depending on where you ran from:
|
|
16
|
+
|
|
17
|
+
- **Standalone** — the public `@cloudbongos/cli`'s entire situation — no checkout means no brand,
|
|
18
|
+
so every instance shared `~/.config/cloudbongos/gds-session.json`. Signing into a second
|
|
19
|
+
instance **destroyed** the first. Measured on the published 0.1.1: `configHome()` resolved to
|
|
20
|
+
`<HOME>/.config/cloudbongos` whichever instance was named.
|
|
21
|
+
- **In-repo** the brand resolved and the session landed in `~/.config/<slug>/`, where the
|
|
22
|
+
standalone CLI could never find it. `npx @cloudbongos/cli api GET /api/gds/me` from a bare
|
|
23
|
+
directory returned the cloudbongos.com builder while a valid hermeslines session sat on the same
|
|
24
|
+
machine.
|
|
25
|
+
|
|
26
|
+
This was not theoretical. The owner's own config dir carries a hand-made
|
|
27
|
+
`gds-session.<redacted>.bak.json`, alongside four more rescues of the same
|
|
28
|
+
shape. For a newcomer with one project it is invisible; it bites precisely the people running more
|
|
29
|
+
than one, which is the direction the platform is going.
|
|
30
|
+
|
|
31
|
+
## Decision
|
|
32
|
+
|
|
33
|
+
**Keep one session file per instance, keyed by the instance's host, under a FIXED anchor —
|
|
34
|
+
`~/.config/<FALLBACK_DIR>/instances/<host>.json` — and leave the per-brand `gds-session.json`
|
|
35
|
+
exactly where it is, as the ACTIVE pointer.**
|
|
36
|
+
|
|
37
|
+
Four parts are load-bearing.
|
|
38
|
+
|
|
39
|
+
### 1. The anchor is fixed, never brand-derived
|
|
40
|
+
|
|
41
|
+
A brand-derived anchor is the bug. `sessionStoreDir()` is built from `FALLBACK_DIR` and must not
|
|
42
|
+
read `configHome()`, `configDirName()` or the pack — so the same instance resolves to the same file
|
|
43
|
+
whether you run from a checkout or from `npx`. A test asserts the function body names none of them,
|
|
44
|
+
because "helpfully" routing it back through the brand would silently reinstate the split.
|
|
45
|
+
|
|
46
|
+
It lives in `scripts/gds/cli-lib.js`, **not** `src/instance-config.js`. That module is generic
|
|
47
|
+
config-path machinery shared with the server, and a CLI session store is not its concern; cli-lib
|
|
48
|
+
already owns `SESSION_PATH` and `SESSION_READ_PATHS`, so one file now describes where a session
|
|
49
|
+
lives. (The first cut put it in instance-config and the knip dead-code ratchet caught it: knip
|
|
50
|
+
cannot trace that module's `require()` consumers, so every export it gains counts as dead — three
|
|
51
|
+
new ones broke the gate. The right answer was better placement, not a bigger baseline.)
|
|
52
|
+
|
|
53
|
+
### 2. The active pointer does not move
|
|
54
|
+
|
|
55
|
+
Every existing reader — in-repo skills, the card hook, the dev box, `readSessionToken` — looks at
|
|
56
|
+
`gds-session.json` in the configured dir. The store is **additive**: `saveSession` still writes that
|
|
57
|
+
file last and unchanged. Moving it would have been the tidier design and would have broken every one
|
|
58
|
+
of those readers at once, for no gain a builder can see.
|
|
59
|
+
|
|
60
|
+
### 3. The OUTGOING session is archived before the incoming one lands
|
|
61
|
+
|
|
62
|
+
Order matters, and this step is the one that is easy to omit. A session written *before* the store
|
|
63
|
+
existed is not in it; without archiving the session being replaced, the very first login after
|
|
64
|
+
upgrading would still lose it — the exact bug, surviving its own fix. Archiving is best-effort and
|
|
65
|
+
never blocks a real sign-in.
|
|
66
|
+
|
|
67
|
+
### 4. A stored token is verified before it is trusted
|
|
68
|
+
|
|
69
|
+
`bongos login <url>` now switches back to an instance you are already signed into with no browser
|
|
70
|
+
flow — but it calls `/api/gds/me` with the stored token first. Reinstating an expired or revoked
|
|
71
|
+
token unchecked would leave a builder "signed in" to a session every later command then fails on.
|
|
72
|
+
An unverified token falls straight through to the real device flow; `--force` signs in as somebody
|
|
73
|
+
else.
|
|
74
|
+
|
|
75
|
+
Because sessions are now kept rather than overwritten, `login` also names the other instances this
|
|
76
|
+
machine knows and how to move between them — silence would leave them unfindable.
|
|
77
|
+
|
|
78
|
+
## Consequences
|
|
79
|
+
|
|
80
|
+
- Signing into a second instance is non-destructive, and moving between two projects costs no
|
|
81
|
+
browser round-trip.
|
|
82
|
+
- Session files are `0600` in a `0700` directory: they hold bearer tokens.
|
|
83
|
+
- An `api_base` that names no host is **not stored** rather than stored under a guessed name, so a
|
|
84
|
+
malformed or hostile base cannot write outside the store directory.
|
|
85
|
+
- **The directory is `instances/`, not `sessions/`.** `sessions` is a live module key, and
|
|
86
|
+
ADR 0083 §Decision #4 forbids kernel machinery from naming one — `src/instance-config.js` is a
|
|
87
|
+
kernel file and the fitness gate caught it. `instances/` is also the truer name: one file per
|
|
88
|
+
instance is exactly what it holds.
|
|
89
|
+
- A corrupt entry in the store is skipped, never fatal.
|
|
90
|
+
- Still open: the store is per-machine and holds no notion of which instance a *directory* belongs
|
|
91
|
+
to, so running in an instance repo does not automatically select that instance — the active
|
|
92
|
+
pointer is whatever you last signed into. That is a smaller, separable question.
|
|
93
|
+
|
|
94
|
+
## Rejected
|
|
95
|
+
|
|
96
|
+
- **Moving the active pointer into the store** — breaks every existing reader at once for no
|
|
97
|
+
visible gain.
|
|
98
|
+
- **One file holding a map of instances** — changes the on-disk format that in-repo readers,
|
|
99
|
+
hooks and the dev box all parse today.
|
|
100
|
+
- **Keying the store under `configHome()`** — brand-derived, which is the bug being fixed.
|
|
101
|
+
- **Restoring a stored session without verifying it** — hands the builder a dead session that
|
|
102
|
+
looks live.
|
|
103
|
+
- **A new `bongos switch` verb** — `login <url>` is the command a builder already reaches for, and
|
|
104
|
+
the public CLI's verb surface is deliberately small (ADR 0258).
|