@bongos/core 1.19.552 → 1.19.554
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 +26 -21
- package/docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md +133 -0
- package/docs/adr/README.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/builders.js +4 -4
- package/modules/hall-ui/public/hall-render.js +1 -1
- package/modules/hall-ui/public/idea.js +1 -1
- package/modules/hall-ui/public/task-lib.js +2 -2
- package/modules/hall-ui/public/task.css +1 -1
- package/modules/hall-ui/public/task.js +3 -3
- package/modules/hall-ui/public/work.js +2 -2
- package/modules/public-landing/public/index.html +3 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-docs/index.html +1 -1
- package/src/module-api.js +1 -1
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
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.554",
|
|
6
|
+
"core_contract": "1.19.554",
|
|
7
|
+
"source_commit": "215fb366ac46d6cf833c1e4eee47e40a9052f39a",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-06T16:09:58.941Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 443,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
14
|
"functional_verbatim": 2019,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2486,
|
|
20
|
+
"tree_sha256": "c3e20074c1896a0d3a40217b84ab0a534a63a1329a664c419b5e7d0f942db7af",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -1764,10 +1764,15 @@
|
|
|
1764
1764
|
"mode": "0000644",
|
|
1765
1765
|
"sha256": "38fa5f4ce3a91eaf3302527a4228ddb1951ea128e83b2ab8e0ad5def658e0a6d"
|
|
1766
1766
|
},
|
|
1767
|
+
{
|
|
1768
|
+
"path": "docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md",
|
|
1769
|
+
"mode": "0000644",
|
|
1770
|
+
"sha256": "da4549c19609d1f549e6f5a57b530c330765f1778f0a9b09a15a208f4575f986"
|
|
1771
|
+
},
|
|
1767
1772
|
{
|
|
1768
1773
|
"path": "docs/adr/README.md",
|
|
1769
1774
|
"mode": "0000644",
|
|
1770
|
-
"sha256": "
|
|
1775
|
+
"sha256": "f779c7dc040fd7096fa27e713c7045c44d69b1b7fa5fee107e6ae4d69ae11d6e"
|
|
1771
1776
|
},
|
|
1772
1777
|
{
|
|
1773
1778
|
"path": "docs/api-reference.md",
|
|
@@ -2652,7 +2657,7 @@
|
|
|
2652
2657
|
{
|
|
2653
2658
|
"path": "docs/module-api-changelog.md",
|
|
2654
2659
|
"mode": "0000644",
|
|
2655
|
-
"sha256": "
|
|
2660
|
+
"sha256": "e165ea406cc50e9f818a6cc6911a4cdb1ced5ec2be48b991a8e6246c3a22fb31"
|
|
2656
2661
|
},
|
|
2657
2662
|
{
|
|
2658
2663
|
"path": "docs/modules-contract.md",
|
|
@@ -4607,7 +4612,7 @@
|
|
|
4607
4612
|
{
|
|
4608
4613
|
"path": "modules/hall-ui/public/builders.js",
|
|
4609
4614
|
"mode": "0000644",
|
|
4610
|
-
"sha256": "
|
|
4615
|
+
"sha256": "876c4f21b3aaa77fad298915afe7cc481a39b5235e846f61bcc7657a270dd227"
|
|
4611
4616
|
},
|
|
4612
4617
|
{
|
|
4613
4618
|
"path": "modules/hall-ui/public/chime.wav",
|
|
@@ -4822,7 +4827,7 @@
|
|
|
4822
4827
|
{
|
|
4823
4828
|
"path": "modules/hall-ui/public/hall-render.js",
|
|
4824
4829
|
"mode": "0000644",
|
|
4825
|
-
"sha256": "
|
|
4830
|
+
"sha256": "df47881aa19503356bc54c04ffca1167effb68f43b5c442e909074744f9af63c"
|
|
4826
4831
|
},
|
|
4827
4832
|
{
|
|
4828
4833
|
"path": "modules/hall-ui/public/hall-widgets.js",
|
|
@@ -4862,7 +4867,7 @@
|
|
|
4862
4867
|
{
|
|
4863
4868
|
"path": "modules/hall-ui/public/idea.js",
|
|
4864
4869
|
"mode": "0000644",
|
|
4865
|
-
"sha256": "
|
|
4870
|
+
"sha256": "e2b0491898652f569a569617c6f3ce969c9011072e04e376355b95d969a09119"
|
|
4866
4871
|
},
|
|
4867
4872
|
{
|
|
4868
4873
|
"path": "modules/hall-ui/public/idea.states.json",
|
|
@@ -5217,12 +5222,12 @@
|
|
|
5217
5222
|
{
|
|
5218
5223
|
"path": "modules/hall-ui/public/task-lib.js",
|
|
5219
5224
|
"mode": "0000644",
|
|
5220
|
-
"sha256": "
|
|
5225
|
+
"sha256": "64cfbc3a3815fd12249a4f8694668b4503272ec7c795ed3eaafbc81ed77183f3"
|
|
5221
5226
|
},
|
|
5222
5227
|
{
|
|
5223
5228
|
"path": "modules/hall-ui/public/task.css",
|
|
5224
5229
|
"mode": "0000644",
|
|
5225
|
-
"sha256": "
|
|
5230
|
+
"sha256": "1f2d0ed8f8c8497468e1102dd2d90b0e63f15bd89158e5935f50de1016c355e4"
|
|
5226
5231
|
},
|
|
5227
5232
|
{
|
|
5228
5233
|
"path": "modules/hall-ui/public/task.html",
|
|
@@ -5232,7 +5237,7 @@
|
|
|
5232
5237
|
{
|
|
5233
5238
|
"path": "modules/hall-ui/public/task.js",
|
|
5234
5239
|
"mode": "0000644",
|
|
5235
|
-
"sha256": "
|
|
5240
|
+
"sha256": "4e4ae2a620f30bc5d8863e0acf75003f97f9071405fceb51fda60de4deb7e3ad"
|
|
5236
5241
|
},
|
|
5237
5242
|
{
|
|
5238
5243
|
"path": "modules/hall-ui/public/task.states.json",
|
|
@@ -5277,7 +5282,7 @@
|
|
|
5277
5282
|
{
|
|
5278
5283
|
"path": "modules/hall-ui/public/work.js",
|
|
5279
5284
|
"mode": "0000644",
|
|
5280
|
-
"sha256": "
|
|
5285
|
+
"sha256": "1fed935dc521c45972e49435308cb791f2c509ea544ff55663221c9723590632"
|
|
5281
5286
|
},
|
|
5282
5287
|
{
|
|
5283
5288
|
"path": "modules/hall-ui/public/work.states.json",
|
|
@@ -6697,7 +6702,7 @@
|
|
|
6697
6702
|
{
|
|
6698
6703
|
"path": "modules/public-landing/public/index.html",
|
|
6699
6704
|
"mode": "0000644",
|
|
6700
|
-
"sha256": "
|
|
6705
|
+
"sha256": "c6ad936d8f4340a16f7faea30ec0b97b7a1c526a9a991d1e1c6ff1833ddb57ce"
|
|
6701
6706
|
},
|
|
6702
6707
|
{
|
|
6703
6708
|
"path": "modules/public-landing/public/index.probes.json",
|
|
@@ -7472,17 +7477,17 @@
|
|
|
7472
7477
|
{
|
|
7473
7478
|
"path": "package-lock.json",
|
|
7474
7479
|
"mode": "0000644",
|
|
7475
|
-
"sha256": "
|
|
7480
|
+
"sha256": "4618ee402fb7f815f739e31bd4b390f79ceed54dcd282eb70ea24b965ec1674b"
|
|
7476
7481
|
},
|
|
7477
7482
|
{
|
|
7478
7483
|
"path": "package.json",
|
|
7479
7484
|
"mode": "0000644",
|
|
7480
|
-
"sha256": "
|
|
7485
|
+
"sha256": "f61bdd8849dbf7bda1fc730194126667db2076b89faacfe1243fa150db4fb5ea"
|
|
7481
7486
|
},
|
|
7482
7487
|
{
|
|
7483
7488
|
"path": "public-docs/index.html",
|
|
7484
7489
|
"mode": "0000644",
|
|
7485
|
-
"sha256": "
|
|
7490
|
+
"sha256": "5ca4ae4f141fb9af378b8bdc0f3a013a6cb3f5289e0b9ba2f5ae5069c4c34844"
|
|
7486
7491
|
},
|
|
7487
7492
|
{
|
|
7488
7493
|
"path": "public-docs/redoc.standalone.js",
|
|
@@ -9172,7 +9177,7 @@
|
|
|
9172
9177
|
{
|
|
9173
9178
|
"path": "src/module-api.js",
|
|
9174
9179
|
"mode": "0000644",
|
|
9175
|
-
"sha256": "
|
|
9180
|
+
"sha256": "2c50343fb6072eb5f1406904bd4e3c0e078019c6573e8e766cbc6662805dac6c"
|
|
9176
9181
|
},
|
|
9177
9182
|
{
|
|
9178
9183
|
"path": "src/module-loader/catalog.js",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# 0250 — Strict versioning: the version boundary is the scope gate
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-06
|
|
5
|
+
- **Tasks:** [#1003589](https://cloudbongos.com/builders#/task/1003589) (this decision), seeded by the planning session [#1003586](https://cloudbongos.com/builders#/task/1003586). The implementation is BV1.R03–R30 under the same goal.
|
|
6
|
+
- **Goal:** [#1000086](https://cloudbongos.com/builders#/goal/1000086) — Strict versioning: a goal cannot outlive its tasks, and a version's goal set is closed.
|
|
7
|
+
- **Builds on:** [ADR 0086](<redacted>.md) (Project → Version → Goal → Criteria → Task — the hierarchy this decision finally closes at the top), [ADR 0015](<redacted>.md) (deps are load-bearing, the rank prefix is decorative), [ADR 0183](<redacted>.md) (a criterion satisfies itself when its tasks ship).
|
|
8
|
+
- **Amends:** [ADR 0157](<redacted>.md) — for one key, `version.create`. See §6.
|
|
9
|
+
|
|
10
|
+
## 1. The problem, measured
|
|
11
|
+
|
|
12
|
+
The hierarchy has a bottom and no top. Tasks close, criteria close, goals close —
|
|
13
|
+
and a version never does. Measured on the live instance:
|
|
14
|
+
|
|
15
|
+
| | 2026-09-04 (planning session) | 2026-09-06 (this ADR) |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Goals on BONGOS-V1 | 62 | 64 |
|
|
18
|
+
| Closed goals still holding unfinished tasks | 10, holding 28 | **14, holding 35** (both versions) |
|
|
19
|
+
| Open tasks across open goals | 502 | **523** |
|
|
20
|
+
| Versions `building` on one project | 2 | 2 (`BONGOS-V1`, `CB-V1`) |
|
|
21
|
+
|
|
22
|
+
The second column is not a re-count for its own sake. Two days of ordinary work
|
|
23
|
+
made every number worse, which is the argument: nothing in the system pushes
|
|
24
|
+
back, so the drift is not a backlog to clear once — it is the steady state.
|
|
25
|
+
|
|
26
|
+
"Closed goals still holding unfinished tasks" is the sharp one. Fourteen goals
|
|
27
|
+
report `achieved` or `archived` on every surface a person or an agent reads,
|
|
28
|
+
while thirty-five tasks inside them are still `ready`, `backlog`, `active`,
|
|
29
|
+
`completed` or `confirmed`. A goal that says it is done and is not is worse than
|
|
30
|
+
no goal at all: `/status` rolls it up as delivered, the board stops showing its
|
|
31
|
+
work, and the tasks become unreachable except by direct id.
|
|
32
|
+
|
|
33
|
+
## 2. The cause is mechanical, not social
|
|
34
|
+
|
|
35
|
+
No one decided any of this. Four gaps in the code produce all of it:
|
|
36
|
+
|
|
37
|
+
1. **`POST /goals` never reads the version's status** ([`modules/lifecycle/routes/goals.js`](../../modules/lifecycle/routes/goals.js)). It fetches the version row to validate the id and ignores `status`, so a goal can be added to a version that has been building for months. That is how a version's scope grows without anyone widening it.
|
|
38
|
+
2. **Goal achievement is computed from criteria alone** ([`db-goals.js`](../../modules/lifecycle/db-goals.js) `achieveGoalIfComplete`, and the cascade in [`done-when.js`](../../modules/lifecycle/done-when.js)). Neither writer consults `tasks`. Since [ADR 0183](<redacted>.md) criteria satisfy themselves on ship, the path from "the criteria I happened to link are satisfied" to `achieved` is fully automatic — and a task nobody linked to a criterion holds nothing open.
|
|
39
|
+
3. **`POST /goals/:id/archive` checks nothing at all.** Archiving is a single call that discards whatever the goal was holding, silently.
|
|
40
|
+
4. **No route ships a version.** Version status has only ever moved by hand-run SQL on the droplet; `scripts/gds/version-close.js` prints that the API cannot do it. A thing with no closing move does not close.
|
|
41
|
+
|
|
42
|
+
Each gap is small. Together they mean the version boundary — the one line that is
|
|
43
|
+
supposed to say *this much and no more* — has no enforcement anywhere.
|
|
44
|
+
|
|
45
|
+
## 3. Decision
|
|
46
|
+
|
|
47
|
+
**The version boundary is the scope gate.** A version's goal set is fixed when
|
|
48
|
+
the version starts building; a goal stays open until its work is genuinely
|
|
49
|
+
finished; and a version can reach done. Five rules:
|
|
50
|
+
|
|
51
|
+
**D1 — The goal set closes when the version starts building.** New goals may be
|
|
52
|
+
created freely on a version in `planning` — that is where the *next* version is
|
|
53
|
+
scoped, in advance and at leisure. `POST /goals` reads `version.status` and
|
|
54
|
+
refuses anything else with a named 409 an agent can act on.
|
|
55
|
+
|
|
56
|
+
**D2 — A goal cannot close while it holds non-terminal work.** A goal reaches
|
|
57
|
+
`achieved` only when its criteria are satisfied **and** no task in it is
|
|
58
|
+
non-terminal. Terminal means `shipped` or explicitly `abandoned` — the vocabulary
|
|
59
|
+
declared once in [`modules/lifecycle/task-status.js`](../../modules/lifecycle/task-status.js) ([task #1003588](https://cloudbongos.com/builders#/task/1003588)). `completed` and
|
|
60
|
+
`confirmed` are mid-ship states that can still fail a grade or strand, so they
|
|
61
|
+
hold the goal open. **Shipping is never blocked by this** — only the goal's flip
|
|
62
|
+
to `achieved` is. Both writers gain the check, and the cascade's copy runs on the ship
|
|
63
|
+
transaction's own client so it sees uncommitted flips.
|
|
64
|
+
|
|
65
|
+
**D3 — Killing a goal is one recorded decision.** Archiving a goal that still
|
|
66
|
+
holds open tasks returns those tasks and refuses until the caller says what
|
|
67
|
+
happens to each: abandon with the archive's reason, or move to a named surviving
|
|
68
|
+
goal. Archiving a goal with nothing open stays a single call.
|
|
69
|
+
|
|
70
|
+
**D4 — Every task belongs to a real, open goal.** `goal_id` becomes required at
|
|
71
|
+
creation, the per-version catch-all fallback is deleted, and no vector can put a
|
|
72
|
+
task into a closed goal.
|
|
73
|
+
|
|
74
|
+
**D5 — Exactly one version builds per project, and the next is scopeable while it
|
|
75
|
+
runs.** The server refuses a second `building` version. A version auto-closes
|
|
76
|
+
when its last non-maintenance goal achieves; an Archon may close one early, with
|
|
77
|
+
a per-goal disposition (roll forward as a successor goal, or abandon). The single
|
|
78
|
+
`planning` version promotes itself on close, so the project is never without a
|
|
79
|
+
live train.
|
|
80
|
+
|
|
81
|
+
### The override, and why it is not a loophole
|
|
82
|
+
|
|
83
|
+
D1 without an escape hatch would be a rule people route around instead of using —
|
|
84
|
+
the standard failure of a hard scope gate is a fake "hotfix version" cut to hold
|
|
85
|
+
the thing that did not fit. So: an **Archon may admit one named goal to a building
|
|
86
|
+
version, with a reason**, and the overrides are **counted and shown on the
|
|
87
|
+
version**. A visible counter is what keeps the exception honest; an override that
|
|
88
|
+
leaves no trace is just a slower version of no rule.
|
|
89
|
+
|
|
90
|
+
The maintenance goal is the other pressure valve. Each version carries one
|
|
91
|
+
auto-created maintenance goal, exempt from the auto-close count, whose open bugs
|
|
92
|
+
carry forward — because bugs arrive on their own schedule and must not need an
|
|
93
|
+
override to be filed.
|
|
94
|
+
|
|
95
|
+
## 4. What this is not
|
|
96
|
+
|
|
97
|
+
- **Not a new gate on shipping.** Nothing here can refuse a ship, block a claim, or park a task. Every rule acts on a *closing* move — goal achievement, goal archive, version close, goal/task creation. A builder's day is unchanged unless they try to close something that is not finished.
|
|
98
|
+
- **Not a re-litigation of [ADR 0183](<redacted>.md).** Criteria still satisfy themselves. D2 adds a second condition to the *goal's* flip, not a human gate in front of the criterion's.
|
|
99
|
+
- **Not a change to what `abandoned` means.** [Task 1002651](https://cloudbongos.com/builders#/task/1002651) already made abandoned terminal; D2 relies on that and adds nothing.
|
|
100
|
+
|
|
101
|
+
## 5. Rejected alternatives
|
|
102
|
+
|
|
103
|
+
- **Count `completed`/`confirmed` as finished.** It would close the visible symptom with one word and is wrong: those states can fail a grade, strand at `confirmed`, or be reconciled back. A goal closed on a task that never landed is the same lie in a shorter timeframe. (The goals **board** does bucket them as done — that is a progress ring, and `task-status.js` keeps the two questions apart on purpose.)
|
|
104
|
+
- **A nightly reconciler that reopens wrongly-closed goals.** Repairs the ledger after the fact and leaves the four gaps open, so the report can never be trusted between sweeps. Prevention is available here; detection is what you build when it is not.
|
|
105
|
+
- **A human review gate before a goal may close.** This is the queue [ADR 0183](<redacted>.md) deliberately dismantled. The check D2 needs is a fact the database already holds — asking a person to assert it is slower and less reliable than asking the table.
|
|
106
|
+
- **Allow concurrent building versions and scope them by module.** Tempting, because it is the state the instance is in. But two live trains on one project means every "what is in this version" question has two answers, and the goal set can never close on either. The `planning` version is the supported way to work ahead.
|
|
107
|
+
- **Retire versions entirely and let goals stand alone.** Genuinely considered: goals already carry criteria, membership and scope, so the version is arguably ceremony. Rejected because the version is the unit the owner sets scope in and the unit `limitations/<version>-shipped.md` archives — removing it would move the scope conversation somewhere with no home.
|
|
108
|
+
|
|
109
|
+
## 6. The one amendment to ADR 0157
|
|
110
|
+
|
|
111
|
+
[ADR 0157](<redacted>.md) lowered `version.create` to
|
|
112
|
+
`metic`, on the argument that **authoring delegates, disposition does not**:
|
|
113
|
+
creating a version confers no authority, so its siblings are `goal.create` and
|
|
114
|
+
`task.create`, and only `version.close` stays Archon. That argument was correct
|
|
115
|
+
about what a version *was*.
|
|
116
|
+
|
|
117
|
+
This decision changes what a version *is*. Once the version boundary carries the
|
|
118
|
+
scope gate, cutting a new version is no longer authoring — it is **the way around
|
|
119
|
+
D1**. A builder refused a goal on a building version needs only create a fresh
|
|
120
|
+
version to put it somewhere, and the gate evaporates without anyone overriding
|
|
121
|
+
anything. So `version.create` returns to the Archon floor, and the pairing
|
|
122
|
+
canonical-permissions has always described — close and rank as the powers that
|
|
123
|
+
never delegate — extends to create for the same reason it covered close: it is
|
|
124
|
+
now a disposition over scope, not a piece of authoring.
|
|
125
|
+
|
|
126
|
+
The rest of ADR 0157 stands; this touches exactly one key.
|
|
127
|
+
|
|
128
|
+
## 7. Consequences
|
|
129
|
+
|
|
130
|
+
- The live instance is **not** in the state these rules describe, and the rules cannot be enforced on data that violates them. Bringing it over is explicit work under criterion `sv-cutover-proven-live`: fold `CB-V1` into `BONGOS-V1`, disposition the closed goals holding open tasks, rehome the catch-all tasks, and scope `BONGOS-V2` in `planning`. Each invariant lands with a query proving it.
|
|
131
|
+
- `POST /goals`, `POST /tasks`, `PATCH /tasks/:id` and `POST /goals/:id/archive` gain refusals that did not exist. Every one is a **named** code, because the callers are agents and an unnamed 409 is an agent stopping to ask a human.
|
|
132
|
+
- Anything that created a task without a goal — capture, idea routing, the CLIs — has to name one. That is the point of D4, and it is the widest blast radius here.
|
|
133
|
+
- `/status` and the goal board start telling the truth about what is finished. Some goals that read `achieved` today will read `open` again after the cutover; that is the repair, not a regression.
|
package/docs/adr/README.md
CHANGED
|
@@ -337,6 +337,7 @@ This keeps the decision history honest and traceable.
|
|
|
337
337
|
| 0248 | [**A hub project’s owner is the provisioning row, carried on a port and bound to both halves of the catalog projection** ([task 1003580](https://cloudbongos.com/builders#/task/1003580) · goal 1000046, criterion `proj-invite-builders`). [ADR 0246](<redacted>.md) made a wizard-made project resolvable by `POST /projects/invite` and named the remainder: the route is gated `project.curate` (floor metic) with no own-scope arm, so the project’s own owner — a `xenos` on the hub — got 403 on their own project, blocking BV1.R27–R29 and the C6 proof. The arm admits a `project.curate` holder **OR** the owner, where owner means `provisioning_instances.owner_builder_id` — never the hub’s `owner_github_id` columns, which [ADR 0238](<redacted>.md) fixes as attribution. Three things worth carrying: the fact travels on a NEW port `provisioning.projectOwnedBy` answering a **boolean**, so the comparison stays on the plane that holds the row and no consumer handles an ownership id it could mis-compare; the port is asked about **both halves of the ADR 0246 projection** (slug AND origin), because a hand-seeded catalog row’s name is otherwise a squattable claim on that project’s membership directory; and ownership is asked BEFORE the target is resolved, so the refusal stays the untouched `requirePermission` 403 and cannot become an existence oracle over stealth projects ([ADR 0182](<redacted>.md) D2a). The atom stays spelled in the route’s own middleware list ([ADR 0210](<redacted>.md)’s rule), so the route still classifies `metic+archon`.](<redacted>.md) | onboarding / provisioning / project creation |
|
|
338
338
|
|
|
339
339
|
| 0249 | [**The invite step is the done panel’s first act, not a rail step — and the same widget is the way back** ([task 1002346](https://cloudbongos.com/builders#/task/1002346) · goal 1000046, criterion `proj-invite-builders` / C6). BV1.R27 asks for a skippable invite step *in creation*, but an invite cannot be one of the wizard’s numbered panels: `POST /projects/invite` takes `{ client_id, github_login }` where `client_id` **is the slug**, and per [ADR 0246](<redacted>.md) the catalog row that slug resolves is projected **at create** — so before the Create step there is nothing to invite anyone to, and a pre-Create panel could only queue names and replay them, inventing a second unaudited invite path whose failures land after the owner has left. **Decision: the step is the FIRST act of the DONE panel**, keyed on the created record (not `instanceId`, which `startProgress` sets only *after* `renderDone`), so it also returns on a resumed watch; the rail is NOT renumbered (a captured learning on this file: a renumbering couples in four places, and a step that can only run after the terminal step is not a step in the rail). The SAME widget is mounted again on the project’s own page beside *Its extras* — the [ADR 0245](<redacted>.md) argument, one implementation and two mounts, so the refusal vocabulary cannot drift. The widget answers neither “is anyone signed in” nor “is this their project”: each call site hands over a slug or nothing (the created record; the own-scoped manage read that 404s for everyone else), because a page that re-derived ownership would be a second, weaker copy of a server rule. **The 403 is quoted carefully and that is the point** — [ADR 0248](<redacted>.md) paid two equalised reads per request to make the refusal identical for a real, stealth, absent or malformed target ([ADR 0182](<redacted>.md) D2a), and “you lack permission on **this project**” would assert in copy the existence the route refuses to confirm, so the page says only “this account can’t invite people here” and a test pins that wording. `account_not_found` and `unknown_project` are said in the route’s own words (the latter as a moment, not a dead end — the row is projected at create); anything else falls through to the server’s own message. The pending line is drawn from the 201 row’s `membership_kind` and the typed login — **never the `github_id`** it carries, which names someone who has accepted nothing yet. Creation is never blocked: the project is filed before the step is shown, so every refusal leaves it created and the step re-tryable, and Skip sends nothing. Rejected: a rail step before or after Create; the manage page alone (R27 asks for it *in creation*); reading the real membership directory back (no own-scoped route exists, and the session receipt is honest about being one).](<redacted>.md) | provisioning / project creation |
|
|
340
|
+
| 0250 | [**Strict versioning — the version boundary is the scope gate** ([task 1003589](https://cloudbongos.com/builders#/task/1003589) · goal 1000086). The work hierarchy ([ADR 0086](<redacted>.md)) has a bottom and no top: tasks close, criteria close, goals close, and a version never does. Measured live — **14 goals reading `achieved`/`archived` while holding 35 unfinished tasks**, 523 open tasks across 37 open goals, and **two versions `building` on one project**; every number was worse than the planning session had recorded two days earlier, which is the argument: the drift is the steady state, not a backlog. The cause is four mechanical gaps, not a social one — `POST /goals` fetches the version row and never reads its `status`; goal achievement is computed from criteria alone and never consults `tasks` (both the `db-goals.js` writer and the `done-when.js` cascade); `POST /goals/:id/archive` checks nothing at all; and no route ships a version, so version status has only ever moved as hand-run SQL on the droplet. **Decision, five rules:** the goal set closes when a version starts `building` (new goals go on the `planning` version — where the next version is scoped in advance) with a named 409 an agent can act on; a goal reaches `achieved` only when its criteria are satisfied **and** no task in it is non-terminal, where terminal is `shipped`|`abandoned` per [task 1003588](https://cloudbongos.com/builders#/task/1003588)'s one vocabulary and the mid-ship `completed`/`confirmed` hold it open; archiving a goal that holds open tasks refuses until the caller dispositions each (abandon with the reason, or move to a named surviving goal); `goal_id` becomes required and the per-version catch-all fallback is deleted; and exactly one version builds per project, auto-closing when its last non-maintenance goal achieves, with the single `planning` version promoting itself so the project is never without a live train. **The escape hatches are deliberate and visible**: an Archon may admit one *named* goal to a building version with a reason, and the overrides are counted on the version (the standard failure of a hard scope gate is a fake hotfix version cut to hold what did not fit — an untraced override is just a slower version of no rule); plus one auto-created maintenance goal per version, exempt from the auto-close count, so bugs never need an override to be filed. **Nothing here can refuse a ship, a claim, or a task** — every rule acts on a *closing* move. Rejected: counting `completed`/`confirmed` as finished (they can fail a grade or strand — the goals **board** buckets them as done, but that is a progress ring, and `task-status.js` keeps the two questions apart); a nightly reconciler that reopens wrongly-closed goals (detection is what you build when prevention is unavailable, and it is available here); a human review gate before a goal may close (the queue [ADR 0183](<redacted>.md) dismantled — the check is a fact the table already holds); concurrent building versions scoped by module (two live trains means every "what is in this version" question has two answers); retiring versions entirely (the version is the unit the owner sets scope in and `limitations/<version>-shipped.md` archives). **Amends [ADR 0157](<redacted>.md) for exactly one key:** `version.create` returns to the Archon floor. 0157's "authoring delegates, disposition does not" was right about what a version *was* — but once the boundary carries the scope gate, cutting a new version is no longer authoring, it is the way *around* the gate, so create joins close as a disposition over scope.](<redacted>.md) | lifecycle / versioning / scope |
|
|
340
341
|
|
|
341
342
|
| 0246 | [**Catalog membership is a projection of the provisioning row, not a side effect of federation** ([task 1003576](https://cloudbongos.com/builders#/task/1003576) · goal 1000046, criterion `proj-detail-interview` / C3). The BV1.R18 proof ([task 1002337](https://cloudbongos.com/builders#/task/1002337)) demonstrated C3 against live and found its last clause — “then it publishes” — structurally false: the owner’s verdict reads `provisioning_instances` while the public map reads `platform_identity_projects`, and the ONLY bridge was `federateInstance → registerClient → upsertProject`, which `shouldFederate` refuses for anything but `co-tenant` (the wizard hard-codes `standalone`) and which runs ONCE, at provision time, through a COALESCE. So a wizard-made project was never catalogued at all and a co-tenant one that answered later never updated — `publishable: true`, `{"projects": []}`, forever. The same missing row is [task 1003580](https://cloudbongos.com/builders#/task/1003580)’s GAP 1: `POST /projects/invite` resolves against `platform_identity_sso_clients`, whose only writer is that same `registerClient`. Underneath is one conflation — federation mints CREDENTIALS and was incidentally the only thing that minted MEMBERSHIP, while [ADR 0182](<redacted>.md) D4 already fixed the map’s gate at IDENTITY, which has nothing to do with hosting topology. **Decision: the catalog row is a PROJECTION of the provisioning row, re-derived at every write that can change its inputs** — two halves, neither sufficient alone. (1) THE ROW SHAPE: every control-plane project gets both rows whatever its hosting shape, and the client row carries NO credential (`secret_hash = ''`, `redirect_uris = '{}'`), so **the shape is the enforcement** rather than a flag a caller could forget — `redirectUriAllowed` refuses `/sso/authorize`, `clientSecretValid` refuses `/sso/token`, `/sso/membership/check-in` and `/sso/activity/rollup`, and the one leg that checked NEITHER (`/sso/device/start`, which resolves a client from a user-typed origin) grew an explicit `clientIsCredentialed` guard. (2) THE TIMING: re-taken on create, on `PATCH …/detail`, on `PATCH …/settings` when the door changes, and on `POST …/domain` (which re-keys the row, retiring the abandoned origin — [ADR 0181](0181-teardown-state-gate-and-address-retention.md) §2’s leak, in the catalog instead of DNS). **NO MIGRATION** — `secret_hash` is `NOT NULL` and `''` satisfies it while hashing to nothing. `description` and `visibility` are ASSIGNED, not COALESCEd (clearing a description must UN-publish); `featured`/`tagline`/`art_url`/`template` are curation and stay untouched, so [ADR 0182](<redacted>.md) D3 holds. It grants nothing — the row is attribution per [ADR 0238](<redacted>.md), ownership stays `provisioning_instances.owner_builder_id` — and because it writes rows keyed on owner-supplied values (a slug, a BYO domain) each write carries its **claim rule in the ON CONFLICT action’s own WHERE**: the client is refreshed only `WHERE secret_hash = ''` (a real client’s origin, which binds the CLI mint audience, can never be rewritten) and the project only `WHERE owner_github_id = EXCLUDED.owner_github_id` (`NULL = anything` is not true, so the hand-seeded rows cannot be claimed). Writing `visibility` does not contradict [ADR 0182](<redacted>.md) D5: the hub authorizes nothing — the authority already ran on the provisioning plane ([ADR 0238](<redacted>.md)’s own-scoped, audited PATCH), and a project the control plane does NOT own has no provisioning row, so D5’s instance report (R08) stays the only channel for those. `tearing_down`/`torn_down` project `hidden`; a `no_address` instance is not catalogued at all. Travels on the kernel port `project.catalog`, the `project.publishGate` seam in the other direction ([ADR 0083](<redacted>.md) forbids the import). Rejected: extending `shouldFederate` to every shape (hands a real secret to instances whose web.env the platform does not write, and keeps the once-at-standup bug); bridging only at the publish moment (leaves the invite with nothing to resolve); registering only at standup (the bug itself); a `catalog_only` boolean column (needs a migration and is weaker than the shape); letting the bridge write `featured`; and backfilling the three existing catalog rows from code.](<redacted>.md) | provisioning / project creation |
|
|
342
343
|
|
|
@@ -1553,5 +1553,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1553
1553
|
landed since 1.19.550 with no explicit bump. run 33917261720. (task 1002620)
|
|
1554
1554
|
1.19.552 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1555
1555
|
landed since 1.19.551 with no explicit bump. run 34043771286. (task 1002620)
|
|
1556
|
+
1.19.553 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1557
|
+
landed since 1.19.552 with no explicit bump. run 34044208907. (task 1002620)
|
|
1558
|
+
1.19.554 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1559
|
+
landed since 1.19.553 with no explicit bump. run 34044540046. (task 1002620)
|
|
1556
1560
|
---------------------------------------------------------------------------
|
|
1557
1561
|
```
|
|
@@ -335,7 +335,7 @@
|
|
|
335
335
|
|
|
336
336
|
|
|
337
337
|
// Wires the public landing's access-request form. Submits to the one public
|
|
338
|
-
//
|
|
338
|
+
// Bongos write (POST /access-requests). On success the form collapses into a
|
|
339
339
|
// confirmation so the stranger knows the Archon has been pinged.
|
|
340
340
|
|
|
341
341
|
// Reads the module-level lastKarmaState / lastCostState directly — every caller
|
|
@@ -427,7 +427,7 @@
|
|
|
427
427
|
// * pick_disciplines — the craft picker (PATCH /me/disciplines).
|
|
428
428
|
// * join_discord — a Connect-Discord button (the existing OAuth flow,
|
|
429
429
|
// auto-joins + assigns rank role); clears when linked.
|
|
430
|
-
// cli_setup clears once the
|
|
430
|
+
// cli_setup clears once the Bongos CLI is authenticated (db.hasCliSession) and
|
|
431
431
|
// ship_three on the 3rd ship — both passive. The terminal ship_three step
|
|
432
432
|
// shows the live "N of 3" counter (its `detail`). `discord` is the /me discord
|
|
433
433
|
// block ({ linked, configured }) — when Discord isn't configured server-side
|
|
@@ -972,7 +972,7 @@
|
|
|
972
972
|
// local action or page reload while the hall sits open:
|
|
973
973
|
// * box_connect — stamped by the box's heartbeat the first time a `claude`
|
|
974
974
|
// process runs on the dev box (builder_boxes.first_connected_at);
|
|
975
|
-
// * cli_setup — stamped when the
|
|
975
|
+
// * cli_setup — stamped when the Bongos CLI is first authenticated on the box;
|
|
976
976
|
// * join_discord — flips on the OAuth link (usually a full redirect, but can
|
|
977
977
|
// also land from another tab).
|
|
978
978
|
// Without a poll, a newcomer who connects their box AFTER opening the hall sees
|
|
@@ -1268,7 +1268,7 @@
|
|
|
1268
1268
|
// The hall used to fetch every panel once at load and then freeze until a
|
|
1269
1269
|
// manual refresh — so an onboarding step that ticked server-side (e.g. after
|
|
1270
1270
|
// /builder-ship) never showed. We now open a Server-Sent Events stream
|
|
1271
|
-
// (GET /api/bongos/live) that emits a tiny `nudge` whenever
|
|
1271
|
+
// (GET /api/bongos/live) that emits a tiny `nudge` whenever Bongos state changes via
|
|
1272
1272
|
// ANY write path — API routes, background routines, even direct psql writes
|
|
1273
1273
|
// (the trigger lives in the DB; see migration 066 + src/bongos/routes/live.js).
|
|
1274
1274
|
// On a nudge we re-run loadAll(), debounced so a burst coalesces into one
|
|
@@ -916,7 +916,7 @@
|
|
|
916
916
|
// time. (The Discord step already did this; the craft picker and the
|
|
917
917
|
// See-the-works link used to render on locked/cleared steps too.) Every
|
|
918
918
|
// step auto-detects its own completion server-side (#777): join_discord
|
|
919
|
-
// clears on the Discord link, cli_setup once the
|
|
919
|
+
// clears on the Discord link, cli_setup once the Bongos CLI is authenticated,
|
|
920
920
|
// ship_three at the third ship — no manual ticking.
|
|
921
921
|
let action = '';
|
|
922
922
|
if (s.is_current && !s.cleared) {
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// The body is markdown and renders through the hall's one parser, the way the
|
|
117
|
-
// task record's description does (task 1003465); a bare #NNN is a
|
|
117
|
+
// task record's description does (task 1003465); a bare #NNN is a Bongos id and
|
|
118
118
|
// drops its hash so it cannot read as a GitHub issue. Without the reader the
|
|
119
119
|
// text still shows, escaped.
|
|
120
120
|
function prose(text) {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
return `<span class="task__tag task__tag--rank" title="Minimum rank required to claim this task">${esc(label)}</span>`;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
// Strip bare "#NNN"
|
|
68
|
+
// Strip bare "#NNN" Bongos refs from prose we are about to paste elsewhere: on
|
|
69
69
|
// GitHub a bare #NNN autolinks to an ISSUE, and our ids are not GitHub's, so
|
|
70
70
|
// the link 404s. (Same rule the repo enforces in committed markdown.)
|
|
71
71
|
// VERBATIM from the two originals — do not "tidy" either detail:
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
|
|
96
96
|
const hallUrl = `${hallBase}#/task/${t.id}`;
|
|
97
97
|
return [
|
|
98
|
-
`I am working on
|
|
98
|
+
`I am working on Bongos task ${t.id} (${t.title}) — ${hallUrl}`,
|
|
99
99
|
`Read the task description, follow its acceptance criteria, stay strictly within touches[], and ship via /builder-ship when done.`,
|
|
100
100
|
``,
|
|
101
101
|
`Task description:`,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
beats with exactly ONE lit, which is the same lit-step grammar as the open tab
|
|
16
16
|
and the current nav item. It is a READOUT, not a control — nothing here is
|
|
17
17
|
pressable, so the accent lights only the beat the task is actually on.
|
|
18
|
-
The six
|
|
18
|
+
The six Bongos statuses fold into five beats because backlog / blocked / ready
|
|
19
19
|
are all "no builder has it yet"; task.js writes the caption that says which. */
|
|
20
20
|
.tk-rail {
|
|
21
21
|
display: grid;
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
return `<section><p class="rec-sec__h">${escapeHtml(kindWord)}</p><ul class="rec-deplist">${rows}</ul></section>`;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
// The lifecycle rail: five beats, exactly one lit. Six
|
|
247
|
+
// The lifecycle rail: five beats, exactly one lit. Six Bongos statuses fold into
|
|
248
248
|
// five because backlog / blocked / ready all mean "no builder holds it yet";
|
|
249
249
|
// the first beat's caption is what says WHICH of the three it is, so the rail
|
|
250
250
|
// never implies a claim that has not happened.
|
|
@@ -321,7 +321,7 @@
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
// A bare #NNN in a description is a
|
|
324
|
+
// A bare #NNN in a description is a Bongos id, and anywhere it is pasted or
|
|
325
325
|
// rendered it would autolink to a GitHub issue that does not exist (CLAUDE.md's
|
|
326
326
|
// no-bare-#NNN rule) — so both the copy and the rendered paths drop the hash.
|
|
327
327
|
const delinkRefs = (s) => String(s || '').replace(/(^|\s)#(\d+)\b/g, '$1$2');
|
|
@@ -343,7 +343,7 @@
|
|
|
343
343
|
function buildPrompt(t) {
|
|
344
344
|
const url = `${location.origin}${hallBase()}/task/${t.id}`;
|
|
345
345
|
const desc = delinkRefs(t.description);
|
|
346
|
-
return `I am working on
|
|
346
|
+
return `I am working on Bongos task ${t.id} (${t.title || ''}) — ${url}\n\n` +
|
|
347
347
|
`Claim it with \`/builder-claim ${t.id}\`, build it, then \`/builder-ship ${t.id}\`.\n\n` +
|
|
348
348
|
`Task description:\n${desc}`;
|
|
349
349
|
}
|
|
@@ -817,7 +817,7 @@
|
|
|
817
817
|
function buildBulkParallelPrompt(tasks) {
|
|
818
818
|
const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
|
|
819
819
|
const lines = [
|
|
820
|
-
`I want to work on ${tasks.length}
|
|
820
|
+
`I want to work on ${tasks.length} Bongos tasks IN PARALLEL — open ${tasks.length} separate Claude Code sessions, each in its own worktree, one per task below, so they can run at the same time without touching each other's files.`,
|
|
821
821
|
'',
|
|
822
822
|
];
|
|
823
823
|
tasks.forEach((t, i) => {
|
|
@@ -831,7 +831,7 @@
|
|
|
831
831
|
function buildBulkSequentialPrompt(tasks) {
|
|
832
832
|
const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
|
|
833
833
|
const lines = [
|
|
834
|
-
`I want to work on ${tasks.length}
|
|
834
|
+
`I want to work on ${tasks.length} Bongos tasks IN ORDER, one after another in this same session — claim, build, and ship each before moving to the next.`,
|
|
835
835
|
'',
|
|
836
836
|
];
|
|
837
837
|
tasks.forEach((t, i) => {
|
|
@@ -910,7 +910,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
910
910
|
<script>
|
|
911
911
|
(function () {
|
|
912
912
|
var API = '/api/bongos';
|
|
913
|
-
var
|
|
913
|
+
var API_ROOT = '/api/gds';
|
|
914
914
|
|
|
915
915
|
/* ── the tagline. It is ONE server-stamped string, but the composition wants it
|
|
916
916
|
on two lines, so the two halves are baked as separate spans. A host whose
|
|
@@ -1109,7 +1109,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
1109
1109
|
return d.getDate() + ' ' + MONTHS[d.getMonth()];
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
|
-
json(
|
|
1112
|
+
json(API_ROOT + '/public/progress').then(function (data) {
|
|
1113
1113
|
var rows = (data && data.progress) || [];
|
|
1114
1114
|
if (!rows.length) return;
|
|
1115
1115
|
var live = null;
|
|
@@ -1129,7 +1129,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
1129
1129
|
}
|
|
1130
1130
|
});
|
|
1131
1131
|
|
|
1132
|
-
json(
|
|
1132
|
+
json(API_ROOT + '/public/recent-shipped?limit=6').then(function (data) {
|
|
1133
1133
|
var tasks = ((data && data.tasks) || []).slice(0, 6);
|
|
1134
1134
|
var list = document.querySelector('[data-rows]');
|
|
1135
1135
|
if (!tasks.length || !list) return;
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.554",
|
|
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.554",
|
|
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.554",
|
|
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/public-docs/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>{{productName}} — API docs</title>
|
|
7
|
-
<meta name="description" content="{{productName}}
|
|
7
|
+
<meta name="description" content="{{productName}} API reference (OpenAPI 3.1)." />
|
|
8
8
|
<style>
|
|
9
9
|
/* Self-contained: no external fonts/CSS. Redoc paints the rest. */
|
|
10
10
|
html, body { margin: 0; padding: 0; }
|
package/src/module-api.js
CHANGED
|
@@ -49,7 +49,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
49
49
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
50
50
|
// the entry to that file. Look for a version's history there, not here.
|
|
51
51
|
// ---------------------------------------------------------------------------
|
|
52
|
-
const CORE_VERSION = '1.19.
|
|
52
|
+
const CORE_VERSION = '1.19.554'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
53
53
|
|
|
54
54
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
55
55
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|