@bongos/core 1.19.896 → 1.19.897
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 +30 -15
- package/docs/adr/0311-an-idea-is-a-permanent-origin-tag.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/design/ideator-surface-direction.md +77 -0
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/gds-literal-scan.js +22 -1
- package/scripts/gds/seed-bv2-ideator-surface-tasks.js +399 -0
- package/src/module-api.js +1 -1
- package/tests/fitness_gds_ratchet.mjs +18 -0
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.897",
|
|
6
|
+
"core_contract": "1.19.897",
|
|
7
|
+
"source_commit": "bee6c40d5b1e4600a59cace9b3068cfb1626de0d",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-20T02:58:37.233Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 500,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2277,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2803,
|
|
20
|
+
"tree_sha256": "649ca49d792d2d5a87fa5ae51bad0373270f0551dc7d8f82929bfa9112592e21",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2044,10 +2044,15 @@
|
|
|
2044
2044
|
"mode": "0000644",
|
|
2045
2045
|
"sha256": "9c8eff827f7c676cb8fb73475383812746d31c9ca35e2ec4ccb3845c48d2cd63"
|
|
2046
2046
|
},
|
|
2047
|
+
{
|
|
2048
|
+
"path": "docs/adr/0311-an-idea-is-a-permanent-origin-tag.md",
|
|
2049
|
+
"mode": "0000644",
|
|
2050
|
+
"sha256": "6046995188cef5e8652787eca38ccc6a69393f0985891764d6573f3379bbe6ab"
|
|
2051
|
+
},
|
|
2047
2052
|
{
|
|
2048
2053
|
"path": "docs/adr/README.md",
|
|
2049
2054
|
"mode": "0000644",
|
|
2050
|
-
"sha256": "
|
|
2055
|
+
"sha256": "16fdd3b239a9a161d70e3432c35705590d0103f798f2e5f00db53cffcbf5cb81"
|
|
2051
2056
|
},
|
|
2052
2057
|
{
|
|
2053
2058
|
"path": "docs/api-reference.md",
|
|
@@ -2114,6 +2119,11 @@
|
|
|
2114
2119
|
"mode": "0000644",
|
|
2115
2120
|
"sha256": "67391accbb08e35e37df89e225476bae1b2a8957534a92ba63f49f3f53fcfdeb"
|
|
2116
2121
|
},
|
|
2122
|
+
{
|
|
2123
|
+
"path": "docs/design/ideator-surface-direction.md",
|
|
2124
|
+
"mode": "0000644",
|
|
2125
|
+
"sha256": "0ca62887b7345136ed669d2a6c279cd59c0cea2dce9cc91a3af1680ce0a31449"
|
|
2126
|
+
},
|
|
2117
2127
|
{
|
|
2118
2128
|
"path": "docs/design/landing-page-direction-v2.md",
|
|
2119
2129
|
"mode": "0000644",
|
|
@@ -2612,7 +2622,7 @@
|
|
|
2612
2622
|
{
|
|
2613
2623
|
"path": "docs/module-api-changelog.md",
|
|
2614
2624
|
"mode": "0000644",
|
|
2615
|
-
"sha256": "
|
|
2625
|
+
"sha256": "a14d4e32aa8cd8793cdc535db0aa0047d7381de2e95cd6dd629c15b3ba0089b5"
|
|
2616
2626
|
},
|
|
2617
2627
|
{
|
|
2618
2628
|
"path": "docs/modules-contract.md",
|
|
@@ -7722,12 +7732,12 @@
|
|
|
7722
7732
|
{
|
|
7723
7733
|
"path": "package-lock.json",
|
|
7724
7734
|
"mode": "0000644",
|
|
7725
|
-
"sha256": "
|
|
7735
|
+
"sha256": "ea53354ce229c7ec74a1909c0d0153363ee9f18969b0916c6dc832e36d2e9e8f"
|
|
7726
7736
|
},
|
|
7727
7737
|
{
|
|
7728
7738
|
"path": "package.json",
|
|
7729
7739
|
"mode": "0000644",
|
|
7730
|
-
"sha256": "
|
|
7740
|
+
"sha256": "6cec6fa876274d23ee1da877b77841482b5b2558ddebac5adad801535deffa0d"
|
|
7731
7741
|
},
|
|
7732
7742
|
{
|
|
7733
7743
|
"path": "public-docs/index.html",
|
|
@@ -8352,7 +8362,7 @@
|
|
|
8352
8362
|
{
|
|
8353
8363
|
"path": "scripts/gds/gds-literal-scan.js",
|
|
8354
8364
|
"mode": "0000644",
|
|
8355
|
-
"sha256": "
|
|
8365
|
+
"sha256": "c7722e0ea9f0b6f1bc6cd2f791fd79f148861bd9c52eaf910859efebc6a13e94"
|
|
8356
8366
|
},
|
|
8357
8367
|
{
|
|
8358
8368
|
"path": "scripts/gds/gen-api-client.js",
|
|
@@ -8949,6 +8959,11 @@
|
|
|
8949
8959
|
"mode": "0000644",
|
|
8950
8960
|
"sha256": "fe6f67019515067a6147506ad5a29fcb38df06f66239b879ed9acf7d16f1b06a"
|
|
8951
8961
|
},
|
|
8962
|
+
{
|
|
8963
|
+
"path": "scripts/gds/seed-bv2-ideator-surface-tasks.js",
|
|
8964
|
+
"mode": "0000644",
|
|
8965
|
+
"sha256": "1e74a2b1582922bbab8be30e62f8276f9d22e69d42210783940abcfcb618c603"
|
|
8966
|
+
},
|
|
8952
8967
|
{
|
|
8953
8968
|
"path": "scripts/gds/seed-gds-v4-tasks.js",
|
|
8954
8969
|
"mode": "0000644",
|
|
@@ -9732,7 +9747,7 @@
|
|
|
9732
9747
|
{
|
|
9733
9748
|
"path": "src/module-api.js",
|
|
9734
9749
|
"mode": "0000644",
|
|
9735
|
-
"sha256": "
|
|
9750
|
+
"sha256": "dc291210b4d1b471b7e946fc451381204d9eece987e0d0df49f364e41a240594"
|
|
9736
9751
|
},
|
|
9737
9752
|
{
|
|
9738
9753
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11032,7 +11047,7 @@
|
|
|
11032
11047
|
{
|
|
11033
11048
|
"path": "tests/fitness_gds_ratchet.mjs",
|
|
11034
11049
|
"mode": "0000644",
|
|
11035
|
-
"sha256": "
|
|
11050
|
+
"sha256": "230e532b54a69a3ee2c009b3a018a6cb920c92fb402364427cbdc244434d4c1b"
|
|
11036
11051
|
},
|
|
11037
11052
|
{
|
|
11038
11053
|
"path": "tests/fitness_ratchets.mjs",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ADR 0311 — An idea is a permanent origin tag, and the payout follows its descent
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · **Date:** 2026-09-19 · **Task:** [task 1004076](https://cloudbongos.com/builders#/task/1004076) (goal 1000095 — BONGOS-V2)
|
|
4
|
+
|
|
5
|
+
**Related:** [ADR 0262](0262-a-bug-never-lands-in-the-inbox.md) + the landing matrix it governs (`modules/ideas/routing.js`) · [ADR 0175](0175-the-board-room-full-idea-ratification.md) (the Board Room, unchanged by this) · [ADR 0054](0054-session-token-reward-cost-plus.md) (the credit the payout rides on) · [ADR 0015](0015-task-dependencies-and-auto-promotion.md) (why spikes do not auto-promote) · [ADR 0086](0086-goal-scoped-work-hierarchy.md) (goals, which a Full Idea may now become)
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
An idea, today, is a **container for work**. It sits in `idea_inbox` until it names a goal, and then it becomes a task and leaves the ideas world entirely. The better-aimed the thought, the faster it disappears from the person who had it — and the ideas page, which shows only what is left, is named after the leftovers.
|
|
10
|
+
|
|
11
|
+
This was reported as a small confusion and turned out to be structural. On 2026-09-19 the owner filed a thought from the hall, it routed into a goal as a task, the confirmation line was cleared by the next keystroke, and five minutes later they could not find it. The signal existed; its half-life was about four seconds. The routing decision that caused it was made by a `<select>` nobody notices.
|
|
12
|
+
|
|
13
|
+
Three facts found while mapping the live mechanism shaped everything below:
|
|
14
|
+
|
|
15
|
+
- **`karma_log` carries no task key and no idea key**, deliberately (`migrations/040_builder_karma.sql`). "The karma a thought caused" is not a quantity this system can compute.
|
|
16
|
+
- **Task parentage is capped at one level and refuses cross-version parents** (`modules/lifecycle/hierarchy.js`), by design — ship rollup and claim safety lean on it.
|
|
17
|
+
- **Nothing anywhere records that shipped work was later removed.** `shipped` is terminal, there is no reverted state, and `tasks.merged_into_task_id` is CHECK-constrained to abandoned rows, so it can only describe work cut before it landed.
|
|
18
|
+
|
|
19
|
+
## The decision
|
|
20
|
+
|
|
21
|
+
**An idea stops being a container and becomes a permanent origin tag** — never edited, never editing, and the only thing that can answer where a piece of work came from. It may become a task, a criterion or a goal depending on scope; the tag itself never changes.
|
|
22
|
+
|
|
23
|
+
**The tag survives every fork, and the payout follows it.** The ideator's credit split (`modules/economy/credits.js` `awardIdeaTaskShipCredit`) resolves only the earliest lineage link today, so a task split off later pays nobody. It is extended to follow the whole descent, **decaying with depth** — full share at the direct task, halving each hop, so an infinite descent is worth at most twice the direct task. This is what gives the tag teeth: if it does not survive a fork, *someone does not get paid*. Descendant **work** pays the origin; a separate idea that merely cites the origin does not, or early filers farm the project.
|
|
24
|
+
|
|
25
|
+
**Size and brightness are two independent channels.** Size is the sum of those payouts and **never falls**; brightness is how much of that work is **still alive**. A thought that caused a great deal of work the project later tore out renders **large and dead**. A single number would have made that indistinguishable from a thought that never mattered, which is the failure the split exists to prevent.
|
|
26
|
+
|
|
27
|
+
**Karma is rejected as the basis for size** — see Context. Credits are already idea-attributed at ship; karma is not, and is left as it is.
|
|
28
|
+
|
|
29
|
+
**The task hierarchy is not widened.** Provenance is a different relation from work decomposition and belongs with `ideas_task_lineage`, which is already many-ideas-to-one-task and carries none of the hierarchy's invariants.
|
|
30
|
+
|
|
31
|
+
**Removal is proposed by the system and confirmed by a person.** `modules/lifecycle/cascade.js` already implements ship-generates-work and declares an empty rule set; the first rule files a **question** when a ship deletes an earlier shipped task's work. A derived-only signal dims the sky for refactors, renames and file moves; a declared-only one rots because nobody remembers. An unanswered proposal rots on the existing clock; confirming sits at the nod's floor on the nod's existing surface.
|
|
32
|
+
|
|
33
|
+
**A ratified Full Idea becomes the structure, not another point inside it** — a goal, or a criterion inside one. The Full Idea carries a **recommendation** and the board decides which, because scope is the thing you learn last. The five questions are replaced with the owner's set and **versioned**, so ideas already filed keep scoring against the questions they were actually asked. The Board Room flow is unchanged.
|
|
34
|
+
|
|
35
|
+
**Two settings belong to the project, not the platform**, and share one home: who may ignite a thought directly into claimable work, and the decay rate.
|
|
36
|
+
|
|
37
|
+
**Existing ideas get their lineage backfilled, not their payouts.** Size for a pre-existing idea is computed from the descent it has, so the picture is meaningful on day one without spending credits twice — and because that number is then not a sum of payouts actually made, it must be **marked as historical** rather than quietly presented as one.
|
|
38
|
+
|
|
39
|
+
## Consequences
|
|
40
|
+
|
|
41
|
+
- **The hard part is now load-bearing rather than optional.** Before this, a lineage tag that died at the first fork produced a worse picture. After it, it produces a wrong ledger. That is deliberate: it puts the effort where the value is.
|
|
42
|
+
- **Ideator payouts become open-ended in principle**, bounded in practice by the decay. A project that dislikes the shape turns the dial rather than arguing with the platform.
|
|
43
|
+
- **The visible record will be dominated by large dead rings in a project that churns.** Accepted: failure is fine, and a record that only ever grows brighter is not a record.
|
|
44
|
+
- **Three tasks were dropped** as this direction invalidated them — a text-only ancestry trail ([task 1004054](https://cloudbongos.com/builders#/task/1004054)), a `held` inbox status ([task 1002832](https://cloudbongos.com/builders#/task/1002832)), and backfilled Full Idea answers on old goals ([task 1003678](https://cloudbongos.com/builders#/task/1003678)) — and one was amended ([task 1003852](https://cloudbongos.com/builders#/task/1003852)), whose title asserted the exact exclusivity this reverses.
|
|
45
|
+
- **Working area 6 now carries ten criteria.** A consequence of the owner's instruction to add here rather than open a new goal.
|
|
46
|
+
|
|
47
|
+
## Does not decide
|
|
48
|
+
|
|
49
|
+
- The **exact decay rate** beyond its default of halving — it is a per-instance dial, and what a given project should set it to is that project's call.
|
|
50
|
+
- **What precisely counts as removed** — the shape is propose-then-confirm; the derivation is [task 1004081](https://cloudbongos.com/builders#/task/1004081)'s spike to settle.
|
|
51
|
+
- **How provenance edges are physically recorded** — [task 1004080](https://cloudbongos.com/builders#/task/1004080)'s spike, constrained only by "not by widening the task hierarchy".
|
|
52
|
+
- **How any of this is drawn.** The agreed visual direction is `docs/design/ideator-surface-direction.md` and the mockups beside it; the criteria deliberately name the *model* — the states, the channels, the rules — and leave the picture free to change.
|
package/docs/adr/README.md
CHANGED
|
@@ -402,3 +402,4 @@ This keeps the decision history honest and traceable.
|
|
|
402
402
|
| 0308 | [**An eleventh working area: Autobongos** ([task 1003900](https://cloudbongos.com/builders#/task/1003900), goal 1000119). Amends [ADR 0264](0264-the-ten-working-areas.md), which nine days earlier collapsed 38 goals into **ten** areas held until 5,000 builders. Area 11 is Autobongos ([goal 1000119](https://cloudbongos.com/builders#/goal/1000119), owner Lars). **Area 6 was the obvious home and was rejected**: area 6 is about a human's experience of the platform, and this area has no human in it — which limits to spend, which fences hold unsupervised, how it recovers with nobody at the keyboard. Those get less attention next to work with a user in front of it, not better answers. It **reopens** goal 1000028 (*Autonomy groundwork*), archived in the 38→10 cut with no successor and carried as `succeeds_goal_id`, so the lineage is in the ledger rather than the prose — and ADR 0264 §3 had already left "fold, keep as an eleventh, or close" as a live owner question. Admitting it cost something recorded: BONGOS-V2 is `building`, so its goal set is closed (ADR 0250) and `POST /goals` refuses `version_not_planning` — an Archon reopened it with a stored `admit_reason`, which is the cut holding well enough that adding to it left evidence. Does not decide: the ten-area discipline stands (one exception, written reason, 5,000-builder horizon unchanged), 0264 §3's other two open rows, or a second goal for the area.](0308-an-eleventh-working-area.md) | methodology / goals |
|
|
403
403
|
| 0309 | [**Module enablement is a database row, above the committed pack and below env** ([task 1002857](https://cloudbongos.com/builders#/task/1002857), goal 1000091). The hall Modules-tab toggle ([ADR 0107](0107-module-upstreaming-contribution-to-core-catalog.md) §9) wrote `config/modules.json` and reloaded the cache, so it looked like it worked — but that file is the committed instance pack and the pull-deploy runs `git reset --hard origin/main`, so the toggle reverted on the next deploy, **silently**. Enablement moves to `module_enablement_overrides` (migration `core_248`), read as one precedence layer: neutral pack → instance pack → **DB override** → env → default. **Above the pack** because the pack is what a deploy restores, so an override below it would be erased by the event it exists to survive; **below env** because `<PREFIX>_MODULE_<KEY>` is the incident knob and must not need a reachable database. **Auto-committing to `main` stays rejected** ([ADR 0016](<redacted>.md)): durability belongs in the DB, not in an HTTP handler that writes to the default branch. Two open design points settled: the **scope key** is the resolved instance pack path (the only axis two instances sharing one database differ on, so one sibling's toggle cannot flip the other's module), and the DB is read into a **snapshot** refreshed at boot and on write rather than inline, because `loadModules()` is synchronous and making it async would ripple into every consumer of a module flag. An unreachable database is a **no-op** — no row means "no opinion", not "off". Does not decide: whether a toggle should also restart the process so remounting is immediate.](0309-durable-module-enablement-db-override.md) | modules / ops |
|
|
404
404
|
| 0310 | [**A speciality OFFERS skills; the adopter turns them on, one at a time, having been shown what each does** ([task 1003978](https://cloudbongos.com/builders#/task/1003978), goal 1000095). Completes [ADR 0296](0296-a-role-speciality-is-data-over-three-existing-surfaces.md), which defined a speciality as knowledge + skills + an interaction contract and shipped two of the three: `grep -i skill` across the module's schema, domain and routes returns only the header sentences describing a thing that is not there — prose asserting behaviour the code does not have, and the feature's own definition at that. Task 1003978 withheld an implementation until the authority question was answered, and the answer is the strong one: **naming a skill GRANTS NOTHING**. Skills are materialised by `claude-materialize.js` and permission is rank-gated server-side with no caching ([ADR 0016](<redacted>.md)); a speciality offers a menu, and a menu is not an entitlement. **Two layers, because one layer is the blanket the owner ruled against**: the catalog holds what a speciality OFFERS, the already-per-builder `specialities_adoptions` row holds what THIS builder turned on — so two economist ideators share a speciality's knowledge and contract and need not share one enabled skill. **The default is nothing enabled, and that is a position about LEARNING rather than a safe default**: the owner's ruling is that adoption must *walk through what comes with each* because *people need to learn the system*. A settings page of unchecked boxes satisfies the letter and defeats the intent, so the bar is a user test — a person who has finished adopting can say what each skill does. Sharing needs no new mechanism: `owner_kind` / `visibility` / `offered` / `sellable` already ship the private→project→public ladder, and skills ride it. Builds nothing; the three-task order (schema, walkthrough, injection) is filed, and the four documentation sites that claimed a shipped ingredient now say decided-not-built.](0310-a-speciality-offers-skills-and-the-adopter-chooses-them.md) | specialities / methodology |
|
|
405
|
+
| 0311 | [**An idea is a permanent origin tag, and the payout follows its descent** ([task 1004076](https://cloudbongos.com/builders#/task/1004076), goal 1000095 — BONGOS-V2). An idea today is a CONTAINER for work: it leaves `idea_inbox` when it names a goal and lives on as a task, so the better-aimed the thought the faster it vanishes from whoever had it. Reported as a small confusion — a thought filed from the hall routed into a goal, the confirmation line cleared on the next keystroke, and five minutes later the owner could not find it. The idea becomes an **immutable origin tag** instead, the only thing that can answer where a piece of work came from, and **the ideator payout follows its whole descent** rather than stopping at the earliest lineage link (`awardIdeaTaskShipCredit`), decaying by depth — full at the direct task, halving each hop — so an infinite descent is worth at most twice the direct task. That is what gives the tag teeth: if it does not survive a fork, someone does not get paid. **Size and brightness are two channels, not one number**: size is the sum of those payouts and never falls, brightness is how much of that work is still alive, so a thought whose work the project tore out renders LARGE AND DEAD rather than looking like one that never mattered. **Karma was rejected as the basis** — `karma_log` carries no task key and no idea key, deliberately ([`migrations/040_builder_karma.sql`](../../migrations/040_builder_karma.sql)), so "the karma a thought caused" is not computable; credits already are idea-attributed. **The task hierarchy is NOT widened** to carry the descent — it is capped at one level and refuses cross-version parents because ship rollup and claim safety lean on it, and provenance is a different relation from work decomposition. **Nothing records that shipped work was later removed** (`shipped` is terminal, `merged_into_task_id` is CHECK-constrained to abandoned rows), so the first `cascade.js` rule PROPOSES a removal and a person confirms it — derived-only would dim for refactors and renames, declared-only would rot. A ratified **Full Idea becomes a goal or a criterion**, carrying a recommendation the board rules on, because scope is the thing you learn last; the five questions are replaced and VERSIONED so prior filings keep their own. Dropped three tasks this invalidated and amended a fourth whose title asserted the exact exclusivity it reverses. Does not decide: the decay rate beyond its default, what precisely counts as removed, how the edges are physically recorded, or how any of it is drawn.](0311-an-idea-is-a-permanent-origin-tag.md) | ideas / economy |
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Design direction — the ideator surface
|
|
2
|
+
|
|
3
|
+
> **Status:** agreed 2026-09-19 with the area owner. Planning session meta task 1004076; the decisions live on idea 1001072; the scope is `docs/specs/<redacted>.md`.
|
|
4
|
+
>
|
|
5
|
+
> This file is the **visual** half. The spec is the build; the criteria on goal 1000095 are the done-when. Where this file and the spec disagree about behaviour, the spec wins; where a criterion and either of them disagree, the criterion wins.
|
|
6
|
+
|
|
7
|
+
## The problem this answers
|
|
8
|
+
|
|
9
|
+
An idea, today, is a container for work. When it succeeds — when it names a goal and becomes a task — it leaves the ideas world entirely and lives on as a task. So the better aimed the thought, the faster it disappears from the person who had it. The ideas page is named after the leftovers.
|
|
10
|
+
|
|
11
|
+
The session that produced this direction started from a real confusion: a filed thought silently became a task under a goal, the confirmation line vanished on the next keystroke, and the owner could not find what they had written five minutes earlier.
|
|
12
|
+
|
|
13
|
+
## The shift
|
|
14
|
+
|
|
15
|
+
**An idea stops being a container and becomes a permanent origin tag** — never edited, never editing, and the only thing that can answer where a piece of work came from. It can become a task, a criterion or a goal depending on scope, but the tag itself never changes.
|
|
16
|
+
|
|
17
|
+
Two flows, not one:
|
|
18
|
+
|
|
19
|
+
- **New ideas file fast.** One line, one gesture. Aiming is optional. Nothing about filing gets slower.
|
|
20
|
+
- **Past ideas are explored as a descent** — one thought at the top, everything that grew from it falling away below.
|
|
21
|
+
|
|
22
|
+
## The world
|
|
23
|
+
|
|
24
|
+
The project's own design system (`DESIGN.md`, "Cloud Bongos — The Chrome World") already supplies the vocabulary: a pure black void, hairline structure, and one warm salmon accent whose single law is that it **lights only what is pressable or live**. That law is why this direction reads as a sky without inventing anything: a living thing glows, a dead thing does not, and the accent's rarity is what makes it believed.
|
|
25
|
+
|
|
26
|
+
### Gravity
|
|
27
|
+
|
|
28
|
+
The project's open goals are gravity wells. A thought released near one is caught by it and becomes work inside it; a thought nothing catches floats in open space.
|
|
29
|
+
|
|
30
|
+
This is **not new machinery**. It is the capture-time routing that already runs, drawn instead of hidden inside a `<select>`. The invisible dropdown is the direct cause of the confusion that started the session, so making the catch visible is the fix, not a decoration on top of it.
|
|
31
|
+
|
|
32
|
+
A goal's **criteria are the rings inside its well**, closing as each is satisfied.
|
|
33
|
+
|
|
34
|
+
### The five objects
|
|
35
|
+
|
|
36
|
+
Dim and dark are different states and must be different objects, not one dot at two opacities.
|
|
37
|
+
|
|
38
|
+
| Object | What it means |
|
|
39
|
+
|---|---|
|
|
40
|
+
| **star** | caused work that is still alive. it dims as pieces of that work are removed |
|
|
41
|
+
| **protostar** | filed, not yet ignited. inside a well it awaits a nod; in open space nothing has claimed it |
|
|
42
|
+
| **satellite** | folded into another thought. it orbits that one now, and its value counts there |
|
|
43
|
+
| **remnant** | it burned, then the project removed it. it keeps every bit of its size |
|
|
44
|
+
| **cold body** | declined at triage. it never ignited, so it has no size at all |
|
|
45
|
+
|
|
46
|
+
Position carries a distinction the object alone cannot: a protostar **inside a well** has a home and is waiting for a nod; a protostar in **open space** has no home at all. That makes the nod queue something a person can see rather than a list someone must remember to open.
|
|
47
|
+
|
|
48
|
+
### Size and brightness are two channels
|
|
49
|
+
|
|
50
|
+
- **Size** is what a thought caused — the ideator's total payout from its descent — and it **never falls**.
|
|
51
|
+
- **Brightness** is how much of that work is **still alive**.
|
|
52
|
+
|
|
53
|
+
Two facts, two encodings. A thought that caused a great deal of work which the project later tore out renders **large and dead**: a big hollow ring. A single number would have made that look identical to a thought that never mattered, which is the failure this split exists to prevent.
|
|
54
|
+
|
|
55
|
+
Karma is not the basis. `karma_log` carries no task key and no idea key and never has, so "the karma a thought caused" is not a quantity the system can compute. Credits are already idea-attributed at ship, so they are.
|
|
56
|
+
|
|
57
|
+
Size is long-tailed, so it is **compressed for drawing** while the true figure is shown in the panel.
|
|
58
|
+
|
|
59
|
+
## Four surfaces
|
|
60
|
+
|
|
61
|
+
The mockup sources are committed at [`mocks/ideator-surface/`](mocks/ideator-surface/) — see that folder's README. They are the agreed shape, not a pixel contract.
|
|
62
|
+
|
|
63
|
+
1. **File** — the fast path. One line, an optional aim, and a receipt that does not evaporate. Filing never gets slower than it is today.
|
|
64
|
+
2. **The room** — the plain, everyday overview: what you filed, what it became, its size and whether it is still burning. This is the page a person opens daily, and it does the real work.
|
|
65
|
+
3. **The descent** — one thought at the top and everything it caused falling away below, through rewrites, splits and merges, including the branches that went out.
|
|
66
|
+
4. **The sky** — the ambient view. It is drawn from numbers the other three already produce, it arrives last, and **it is never the only path to anything it shows**.
|
|
67
|
+
|
|
68
|
+
## What this direction refuses
|
|
69
|
+
|
|
70
|
+
- **A galaxy is not trivial, and it must never be load-bearing.** Every fact the sky shows is reachable without it.
|
|
71
|
+
- **Filing does not get slower to serve the picture.** A spark's whole value is that it costs nothing to have; the sky redraws in the background.
|
|
72
|
+
- **The sky will be dominated by large dead rings in a project that churns.** That is honest, and it is the accepted trade — failure is fine, and a bright sky should be earned.
|
|
73
|
+
- **Brightness never claws back credits.** The ledger is not rewritten when the project changes its mind; only the picture changes.
|
|
74
|
+
|
|
75
|
+
## Provenance
|
|
76
|
+
|
|
77
|
+
Owner-led ideation, 2026-09-19, following the Ideator pack's stance (the human drives, Claude is the sounding board). Every decision above is the owner's, recorded as it was made on idea 1001072. The clickable mockup lives on claude.ai and is linked from that idea; the committed sources beside this file are what survives if that link does not.
|
|
@@ -2266,5 +2266,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2266
2266
|
landed since 1.19.894 with no explicit bump. run 35475074265. (task 1002620)
|
|
2267
2267
|
1.19.896 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2268
2268
|
landed since 1.19.895 with no explicit bump. run 35478072367. (task 1002620)
|
|
2269
|
+
1.19.897 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2270
|
+
landed since 1.19.896 with no explicit bump. run 35485346707. (task 1002620)
|
|
2269
2271
|
---------------------------------------------------------------------------
|
|
2270
2272
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.897",
|
|
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.897",
|
|
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.897",
|
|
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",
|
|
@@ -88,6 +88,25 @@ const GDS_RULE_FILES = new Set([
|
|
|
88
88
|
// legal on any line in any counted file.
|
|
89
89
|
const GDS_VERSION_ID_RE = /GDS-V\d+/g;
|
|
90
90
|
|
|
91
|
+
// An opaque encoded payload is not vocabulary. A committed design mock carries
|
|
92
|
+
// its own content as a base64'd bundle (docs/design/mocks/ideator-surface/
|
|
93
|
+
// ideator-surface.html embeds one in a <script type="__bundler/manifest">), and
|
|
94
|
+
// a three-letter run turns up inside any large base64 blob by chance — five did,
|
|
95
|
+
// which is enough to red the ratchet on a change that introduced no literal at
|
|
96
|
+
// all. The blob cannot be edited to remove them, and the page cannot be
|
|
97
|
+
// committed without it: the manifest IS the page content.
|
|
98
|
+
//
|
|
99
|
+
// So: an unbroken base64-shaped run of 200+ characters is stripped before
|
|
100
|
+
// matching, exactly the way a version id is. The threshold is what makes this
|
|
101
|
+
// safe. The acronym is a WORD wherever it is vocabulary, so it always sits
|
|
102
|
+
// against a boundary — whitespace, punctuation, a case change, an underscore.
|
|
103
|
+
// No line of prose or code reaches 200 unbroken base64 characters, and the
|
|
104
|
+
// blobs that do carry nothing to rename. This is deliberately narrower than a
|
|
105
|
+
// path exclusion on the mocks tree: that tree still counts, so a real literal
|
|
106
|
+
// in a mock's own comment (docs/design/mocks/landing/v1.html:345) is still
|
|
107
|
+
// caught.
|
|
108
|
+
const GDS_ENCODED_BLOB_RE = /[A-Za-z0-9+/=]{200,}/g;
|
|
109
|
+
|
|
91
110
|
// The literal itself: case-sensitive, so the lowercase path/env spellings that
|
|
92
111
|
// criterion C2 owns (`scripts/gds/`, `/api/gds`, `gds_session`) are counted
|
|
93
112
|
// only where they appear in their SHOUTING form. Case-sensitivity is the point
|
|
@@ -105,7 +124,9 @@ function isExcludedPath(relPath) {
|
|
|
105
124
|
// (the checkErrorEnvelope precedent: a gate whose detector drifts from its
|
|
106
125
|
// sweep is worse than no gate).
|
|
107
126
|
function countInText(text) {
|
|
108
|
-
const stripped = text
|
|
127
|
+
const stripped = text
|
|
128
|
+
.replace(GDS_ENCODED_BLOB_RE, '')
|
|
129
|
+
.replace(GDS_VERSION_ID_RE, '');
|
|
109
130
|
return (stripped.match(GDS_LITERAL_RE) || []).length;
|
|
110
131
|
}
|
|
111
132
|
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// Seed the tasks for the ideator surface on goal 1000095 (BONGOS-V2, Working area 6).
|
|
2
|
+
// Source of truth: docs/specs/bongos-v2-ideator-surface.md (status: final).
|
|
3
|
+
// Visual direction: docs/design/ideator-surface-direction.md. Decisions: idea 1001072.
|
|
4
|
+
// Planning session 2026-09-19, meta task 1004076.
|
|
5
|
+
//
|
|
6
|
+
// Idempotent on (source, source_ref). Re-running resolves to the existing rows.
|
|
7
|
+
// criterion_ids are SLUGS, never positional "Cn" — goal 1000095 is one of several
|
|
8
|
+
// goals on this version, so a positional ref resolves against the wrong scope and
|
|
9
|
+
// POST /tasks now refuses it outright (learning 1000188, task 1003108).
|
|
10
|
+
//
|
|
11
|
+
// Ends by diffing the criterion links it planned against what the goal actually
|
|
12
|
+
// carries, and exits non-zero on any mismatch (the seeding contract's self-check).
|
|
13
|
+
//
|
|
14
|
+
// Run: node scripts/gds/seed-bv2-ideator-surface-tasks.js
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const { expectedCriterionCounts, verifySeedCriteriaLinks } = require('./seed-verify-criteria');
|
|
17
|
+
const sess = require(os.homedir() + '/.config/cloudbongos/gds-session.json');
|
|
18
|
+
const BASE = sess.api_base.replace(/\/$/, '');
|
|
19
|
+
const TOKEN = sess.token;
|
|
20
|
+
const VER = 'BONGOS-V2';
|
|
21
|
+
const GOAL = 1000095;
|
|
22
|
+
const SRC = 'planning-session';
|
|
23
|
+
const REF_PREFIX = 'bv2-ideator-surface';
|
|
24
|
+
const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
|
|
25
|
+
|
|
26
|
+
const CRIT = {
|
|
27
|
+
lost: 'ideator-nothing-is-lost',
|
|
28
|
+
fork: 'ideator-tag-survives-the-fork',
|
|
29
|
+
sky: 'ideator-sky-tells-the-truth',
|
|
30
|
+
full: 'ideator-full-idea-shapes-the-space',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const SPEC = 'Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.';
|
|
34
|
+
|
|
35
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
36
|
+
async function api(method, path, body, _tries = 0) {
|
|
37
|
+
await sleep(400); // base throttle to stay under the per-IP rate limit
|
|
38
|
+
const res = await fetch(`${BASE}/api/bongos${path}`, {
|
|
39
|
+
method, headers: H, body: body ? JSON.stringify(body) : undefined,
|
|
40
|
+
});
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
let json; try { json = JSON.parse(text); } catch { json = { _raw: text.slice(0, 300) }; }
|
|
43
|
+
if (res.status === 429 && _tries < 8) {
|
|
44
|
+
const wait = ((json.error && json.error.details && json.error.details.retry_after_seconds) || 30) + 1;
|
|
45
|
+
console.log(` …rate-limited, waiting ${wait}s`);
|
|
46
|
+
await sleep(wait * 1000);
|
|
47
|
+
return api(method, path, body, _tries + 1);
|
|
48
|
+
}
|
|
49
|
+
return { status: res.status, ok: res.ok, json };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// TOPOLOGICAL ORDER. The BV2.R## prefix is assigned from this array's index, so
|
|
53
|
+
// for any task A depending on B, B sits earlier and carries the lower number.
|
|
54
|
+
// Nothing else on BONGOS-V2 uses a rank prefix; these 27 carry one so the batch
|
|
55
|
+
// reads in its planned order inside each priority bucket.
|
|
56
|
+
const TASKS = [
|
|
57
|
+
// ---- T1: the two spikes. Neither auto-promotes (kind='spike', ADR 0015).
|
|
58
|
+
{
|
|
59
|
+
ref: 'R07', kind: 'spike', discipline: 'engineer', priority: 5, crit: [CRIT.fork],
|
|
60
|
+
title: 'Spike: how provenance is recorded without widening the task hierarchy',
|
|
61
|
+
desc: `Output an ADR naming the edge shape, where it is written, and who writes it when work is split or rewritten.
|
|
62
|
+
|
|
63
|
+
**The constraint that makes this a spike.** \`modules/lifecycle/hierarchy.js\` caps task parentage at ONE level (a parent may not itself have a parent; a task with children may not become a child) and refuses cross-version trees. Both are deliberate — ship rollup and claim safety lean on them. A descent of the shape idea → task → rewrite → split → removal is four levels and crosses a version boundary, so it cannot ride on that relation, and **widening it is explicitly out of scope** (spec decision 8).
|
|
64
|
+
|
|
65
|
+
Provenance is a different relation from work decomposition. The expected home is \`ideas_task_lineage\`, which is already many-ideas-to-one-task and carries none of those invariants — but confirm that before assuming it.
|
|
66
|
+
|
|
67
|
+
**Also settle:** who writes the edge. Parentage today is a manual explicit act, so an edge that depends on someone remembering will have holes. ${SPEC}`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
ref: 'R14', kind: 'spike', discipline: 'engineer', priority: 5, crit: [CRIT.sky],
|
|
71
|
+
title: 'Spike: what counts as removed, and who says so',
|
|
72
|
+
desc: `Output an ADR settling the removal signal: its source, and whether it is derived or declared.
|
|
73
|
+
|
|
74
|
+
**Nothing records this today.** \`shipped\` is terminal (\`modules/lifecycle/rot.js\` says so in as many words), there is no reverted status, and \`tasks.merged_into_task_id\` is CHECK-constrained to abandoned rows so it can only ever describe work that was cut before it landed. This signal is what lets brightness fall at all.
|
|
75
|
+
|
|
76
|
+
**The owner's decision (spec decision 13) is the shape, not the detail:** the system PROPOSES and a person CONFIRMS. \`modules/lifecycle/cascade.js\` already implements ship-generates-work and ships with an EMPTY rule set, so the mechanism exists and only the rule is new. A derived-only signal dims the sky for refactors, renames and file moves; a declared-only one rots because nobody remembers.
|
|
77
|
+
|
|
78
|
+
Nearest existing signal to build the derivation on: \`modules/economy/credits.js\` \`netNegativeBonusAmount\`, which already reads \`lines_removed > lines_added\` from a ship's own diff — the only place the system counts deletion. ${SPEC}`,
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// ---- Zero-dep surfaces and settings.
|
|
82
|
+
{
|
|
83
|
+
ref: 'R01', kind: 'feature', discipline: 'ui', priority: 4, crit: [CRIT.lost],
|
|
84
|
+
title: 'The receipt persists',
|
|
85
|
+
desc: `A filing's outcome is stated and survives the next keystroke; the last several outcomes stay reachable on the surface that filed them.
|
|
86
|
+
|
|
87
|
+
**This is the bug that started the session.** The hall already prints "Filed as task N under <goal>" on a routed capture (\`modules/hall-ui/public/ideas.js\`, the \`#fq-result\` line) — and then clears it the moment the composer is touched again. The signal exists; its half-life is about four seconds.
|
|
88
|
+
|
|
89
|
+
Done-when: a person files a thought, keeps working, and can still see what became of it without navigating anywhere. ${SPEC}`,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
ref: 'R02', kind: 'feature', discipline: 'ui', priority: 4, crit: [CRIT.lost],
|
|
93
|
+
title: 'Gravity is visible before release',
|
|
94
|
+
desc: `The goal that will catch a thought is shown while composing, and letting it float is an explicit choice rather than an empty dropdown.
|
|
95
|
+
|
|
96
|
+
**No new routing.** \`modules/ideas/routing.js\` \`decideLanding\` already decides this at capture time, and \`GET /goals/suggestions\` already feeds the composer's goal select. This task makes the decision legible at the moment it is made — the invisible dropdown is the direct cause of the confusion this whole scope came from (spec decision 2).
|
|
97
|
+
|
|
98
|
+
Done-when: before releasing a thought, a person can see which goal will catch it, change it, or deliberately let it float. ${SPEC}`,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
ref: 'R05', kind: 'feature', discipline: 'engineer', priority: 3, crit: [CRIT.lost],
|
|
102
|
+
title: 'The per-instance dials: the ignite floor and the decay rate',
|
|
103
|
+
desc: `Both configured rather than constant, sharing one home. Defaults preserve today's behaviour and halving respectively.
|
|
104
|
+
|
|
105
|
+
**Two dials, one surface** (spec decisions 3 and 16). The ignite floor — who may convert a quick idea directly into claimable work — is today the \`METIC_PLUS\` constant in \`modules/ideas/routing.js\`. The decay rate — how fast an ideator's share falls per hop of the descent — is new in R11. The owner's ruling is that both belong to the project, not to the platform: a project of one wants every thought claimable, a project of fifty wants a nod.
|
|
106
|
+
|
|
107
|
+
They are scoped together deliberately so a second dial does not grow its own separate home. Do not widen either default as a side effect. ${SPEC}`,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
ref: 'R20', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.full],
|
|
111
|
+
title: 'The new five questions as a versioned template',
|
|
112
|
+
desc: `\`base-v2\` ships beside \`base-v1\`; ideas already filed keep scoring against the questions they were actually asked.
|
|
113
|
+
|
|
114
|
+
**The owner's question set, in order:** what made you have the idea; scope as best as you can here; what should an engineer or artist know prior to building; how is this long term justified in the project; what does a successful implementation of the idea look like.
|
|
115
|
+
|
|
116
|
+
**This is cheaper than it looks.** \`modules/ideas/templates.js\` already registers templates by key and every idea stores its \`template_key\`, so this is a second entry in the registry, NOT a migration. Renaming a label is free; only renaming a key would be a data migration, and this does not need to.
|
|
117
|
+
|
|
118
|
+
One field survives outright (what made you have the idea ≈ \`why_had\`) and one roughly (the long-term justification ≈ \`fit\`). Three are new and one is dropped. ${SPEC}`,
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// ---- The provenance spine.
|
|
122
|
+
{
|
|
123
|
+
ref: 'R08', kind: 'feature', discipline: 'engineer', priority: 5, crit: [CRIT.fork], deps: ['R07'],
|
|
124
|
+
title: 'Provenance edges on the idea lineage ledger',
|
|
125
|
+
desc: `task→task descent edges recorded, to arbitrary depth and across version boundaries. The task hierarchy's one-level cap and its cross-version refusal are untouched.
|
|
126
|
+
|
|
127
|
+
Build to whatever R07's ADR settled. The write side that exists today is \`modules/ideas/inbox.js\` \`linkIdeaTask\`, an upsert on \`(task_id, idea_id)\`; \`ideas_task_lineage\` has been many-ideas-to-one-task since \`ideas_006\`.
|
|
128
|
+
|
|
129
|
+
**The property that matters:** the tag must survive a rewrite, a split and a merge. If it only survives one hop the result is a link, not a story — and, after R11, someone does not get paid. ${SPEC}`,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
ref: 'R04', kind: 'refactor', discipline: 'ui', priority: 3, crit: [CRIT.lost], deps: ['R01'],
|
|
133
|
+
title: 'Fold the Full Idea form and the spark queue into the new surface',
|
|
134
|
+
desc: `Developing a thought is a step taken AFTER a one-line filing, never a gate before it. No dead links from \`/idea/:id\`, the command palette, or the nav.
|
|
135
|
+
|
|
136
|
+
**What the ideas page actually is today:** three stacked sections — the spark queue ("waiting to be developed"), the quick composer, and the five-field Full Idea composer with its live completeness meter. All three need a home. The Board Room does NOT — it is reached from the left nav and is not behind this page.
|
|
137
|
+
|
|
138
|
+
**The risk this task carries** (spec decision 18): a spark's whole value is that it costs nothing to have. Folding a five-question form onto the same surface is the most likely way to slow the one-line path. R06a exists to catch that; do not treat it as someone else's problem. ${SPEC}`,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
ref: 'R11', kind: 'feature', discipline: 'engineer', priority: 5, crit: [CRIT.fork], deps: ['R08'],
|
|
142
|
+
title: 'The ideator payout follows the descent, decayed by depth',
|
|
143
|
+
desc: `A descendant's ship pays the origin at the decayed rate. Idempotent. Existing first-hop behaviour preserved exactly.
|
|
144
|
+
|
|
145
|
+
**The seam exists.** \`modules/economy/credits.js\` \`awardIdeaTaskShipCredit\` already resolves a shipped task back to its source idea and splits the payout — but only via the EARLIEST lineage link, one hop. A task split off later, or a child of a child, pays nobody.
|
|
146
|
+
|
|
147
|
+
**Decay** (spec decision 16): full share at the direct task, halving at each hop, so an infinite descent is worth at most twice the direct task. The rate is a dial (R05), not a constant.
|
|
148
|
+
|
|
149
|
+
**The boundary that stops farming** (spec decision 7): descendant WORK pays the origin; a separate idea that merely cites the origin does not. The descent displays more than the payout counts. ${SPEC}`,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
ref: 'R09', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.fork], deps: ['R08'],
|
|
153
|
+
title: 'The descent reads to arbitrary depth and across versions',
|
|
154
|
+
desc: `One query, not per-hop round trips.
|
|
155
|
+
|
|
156
|
+
**Three findings inherited from abandoned task 1004054**, whose predecessor was built, graded and lost. They are free design requirements, not someone else's mess:
|
|
157
|
+
|
|
158
|
+
1. **Test at the RENDERED layer, not just the data layer.** The prior attempt tested only the server-side trail builder, which is exactly why finding 2 went uncaught.
|
|
159
|
+
2. **A merged idea's triage note lives on the MERGED row, not the target row.** The prior attempt read it from the target, so the headline scenario was the broken one.
|
|
160
|
+
3. **Collapse the hops into one query or a CTE.** The prior attempt ran 4–6 serial round trips per read.
|
|
161
|
+
|
|
162
|
+
Today's reader is \`modules/ideas/inbox.js\` \`listIdeaTrailForBuilder\`, which returns \`lineage_tasks[]\` — one hop only. ${SPEC}`,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
ref: 'R15', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.sky], deps: ['R14'],
|
|
166
|
+
title: 'The cascade rule that proposes a removal',
|
|
167
|
+
desc: `A ship that deletes an earlier shipped task's work files a QUESTION, not a verdict. \`shipped\` stays terminal and no credit is ever clawed back.
|
|
168
|
+
|
|
169
|
+
Build to R14's ADR. \`modules/lifecycle/cascade.js\` already implements ship-generates-work and declares an EMPTY rule set — this is the first rule, so expect the mechanism to be sound and unexercised.
|
|
170
|
+
|
|
171
|
+
**Do not make this a verdict.** The owner chose propose-then-confirm precisely because refactors, renames and file moves all look like deletions to a diff. A rule that marks work removed on its own evidence will dim the sky for reasons that are not real. ${SPEC}`,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
ref: 'R21', kind: 'feature', discipline: 'engineer', priority: 3, crit: [CRIT.full], deps: ['R20'],
|
|
175
|
+
title: 'Substance floors for the new Full Idea fields',
|
|
176
|
+
desc: `Floors chosen deliberately for each new field, because the completeness score feeds credits.
|
|
177
|
+
|
|
178
|
+
\`modules/ideas/completeness.js\` scores against the per-field \`substanceMin\` declared in the template; \`base-v1\` used a flat 40 characters for all five. The new fields are not interchangeable — "scope as best as you can here" and "what made you have the idea" do not deserve the same floor.
|
|
179
|
+
|
|
180
|
+
**The never-invent-a-field rule gets stronger here, not weaker** (the Ideator pack). The new set is deliberately resistant to ghost-writing: nobody but the filer can answer what made them have the idea. A floor that pushes an agent to pad a thin answer destroys exactly that property. A short answer scores short — that is the scorer working. ${SPEC}`,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
ref: 'R22', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.full], deps: ['R20'],
|
|
184
|
+
title: 'A Full Idea carries a recommendation: goal or criterion',
|
|
185
|
+
desc: `The recommendation is carried from filing through to the board item and shown to voters.
|
|
186
|
+
|
|
187
|
+
**The ideator does not decide** (spec decision 9). They recommend; the board decides. The reasoning the owner accepted is that scope is the thing you learn last — the person who had the thought writes it, the people reading it judge how big it turned out to be.
|
|
188
|
+
|
|
189
|
+
The board item payload is \`boardWindowPayload\` in \`modules/ideas/routes/inbox.js\`; the section labels the Board Room renders are a hand-mirror of the template's field keys (\`modules/hall-ui/public/board-room.js\`, test-pinned) — so a template change and a board change move together. **The Board Room's own flow is unchanged.** ${SPEC}`,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
ref: 'R12', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.fork], deps: ['R11'],
|
|
193
|
+
title: 'Size is the sum of those payouts',
|
|
194
|
+
desc: `Computable from the ledger for any idea. Compressed for drawing, with the true figure available.
|
|
195
|
+
|
|
196
|
+
**Size is credits, not karma** (spec decision 5). \`karma_log\` carries no task key and no idea key — \`migrations/040_builder_karma.sql\` states that this is deliberate — so "the karma a thought caused" is not a quantity that can be computed. Credits are already idea-attributed at ship, so they can.
|
|
197
|
+
|
|
198
|
+
Size never falls. That is what makes it independent of brightness, and what lets a thought whose work was torn out still render large. Sums are long-tailed, so compress for drawing and show the real number in the panel. ${SPEC}`,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
ref: 'R10', kind: 'feature', discipline: 'ui', priority: 4, crit: [CRIT.fork], deps: ['R09'],
|
|
202
|
+
title: 'The descent surface on the idea detail page',
|
|
203
|
+
desc: `One thought at the top, everything it caused falling away below — including the branches that went out.
|
|
204
|
+
|
|
205
|
+
\`/idea/:id\` (\`modules/hall-ui/public/idea.js\`) is the canonical per-idea record and stays; this is its new section. It already renders a one-line "became task N" outcome, which this replaces with the full story.
|
|
206
|
+
|
|
207
|
+
Done-when: a person can trace a thing shipped months later back to the line that caused it, through rewrites, splits and merges. See \`docs/design/mocks/ideator-surface/Descent.dc.html\` for the agreed shape — it is a shape, not a pixel contract. ${SPEC}`,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
ref: 'R15a', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.sky], deps: ['R15'],
|
|
211
|
+
title: 'Confirming or dismissing a removal proposal',
|
|
212
|
+
desc: `On the existing awaiting-nod surface, at the same floor as the nod (Metic+, \`task.promote\`). No new permission and no new page.
|
|
213
|
+
|
|
214
|
+
An unanswered proposal **rots on the normal clock** (spec decision 14) rather than accumulating — \`modules/lifecycle/rot.js\` and the \`GET /inbox/rotting\` feed already exist and this rides them.
|
|
215
|
+
|
|
216
|
+
The surface is \`modules/hall-ui/public/goal-inbox.js\`, which already renders the awaiting-nod queue and the rot feed side by side. Confirming is what makes brightness fall; dismissing is what stops a refactor from dimming the sky. ${SPEC}`,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
ref: 'R23', kind: 'feature', discipline: 'engineer', priority: 3, crit: [CRIT.full], deps: ['R22'],
|
|
220
|
+
title: 'Ratify a Full Idea into a goal',
|
|
221
|
+
desc: `The goal half beside the existing criterion half. One transaction. The five answers land on the goal.
|
|
222
|
+
|
|
223
|
+
**The criterion half already exists** and is the model to follow: \`modules/ideas/inbox.js\` \`ratifyCriterionProposal\` writes \`done_when_criteria\` + the idea status + karma + the audit row in a single transaction, and \`POST /inbox/:id/ratify\` fronts it.
|
|
224
|
+
|
|
225
|
+
**Nothing today turns a filed thought into a goal** — goals come from a separate planning act — so this is the genuinely new path of the four in this criterion. \`POST /goals\` validates \`scope_modules\` against the live roster and escalates to Archon for a protected scope; a ratification path has to respect both. ${SPEC}`,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
ref: 'R12a', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.fork], deps: ['R12'],
|
|
229
|
+
title: 'Backfill lineage for existing ideas, without backfilling money',
|
|
230
|
+
desc: `Pre-existing ideas carry a size computed from the descent they actually have. No credits are paid retroactively.
|
|
231
|
+
|
|
232
|
+
**And the consequence is surfaced, not hidden** (spec decision 17). For those ideas size is NOT a sum of payouts actually made, so it needs a historical marker or the number quietly lies about what it means. The owner chose this deliberately: it makes the sky meaningful on day one without spending credits again on work already paid for once.
|
|
233
|
+
|
|
234
|
+
Done-when: every idea filed before this shipped has a size, and anywhere that size is shown makes clear which kind of number it is. ${SPEC}`,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
ref: 'R03', kind: 'feature', discipline: 'ui', priority: 3, crit: [CRIT.lost], deps: ['R12'],
|
|
238
|
+
title: 'The room',
|
|
239
|
+
desc: `A plain overview of one's own thinking: what was filed, what it became, its size, and whether it still burns.
|
|
240
|
+
|
|
241
|
+
**This is the page a person opens daily**, and it does the real work — the sky is ambient, the room is useful. It is deliberately unglamorous: rows, a size, a state.
|
|
242
|
+
|
|
243
|
+
**It is the author's own view. The profile keeps a compact public trail** (spec decision 19) — the two are not the same surface. \`modules/hall-ui/public/profile.js\` already renders an idea trail from \`GET /inbox/by-builder/:id\`; that stays as what someone else sees on you.
|
|
244
|
+
|
|
245
|
+
See \`docs/design/mocks/ideator-surface/Room.dc.html\` for the agreed shape. ${SPEC}`,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
ref: 'R16', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.sky], deps: ['R15a', 'R12'],
|
|
249
|
+
title: 'Brightness — the live share of a descent',
|
|
250
|
+
desc: `Computable per idea, and it falls when a removal is confirmed.
|
|
251
|
+
|
|
252
|
+
**Graded, not binary** (spec decision 20): a thought whose descent is five things with three still alive is partly lit, and goes out only when nothing survives. That is what makes a star visibly dim as the project removes pieces of what it caused.
|
|
253
|
+
|
|
254
|
+
**Brightness never claws back credits.** The ledger is not rewritten when the project changes its mind — only the picture changes. Size, which R12 owns, does not move. ${SPEC}`,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
ref: 'R06', kind: 'verify', discipline: 'engineer', priority: 3, crit: [CRIT.lost], deps: ['R01', 'R03'],
|
|
258
|
+
title: 'Proof: a thought filed from the hall is traceable an hour later without asking anyone',
|
|
259
|
+
desc: `**The live demonstration that \`ideator-nothing-is-lost\` is met.**
|
|
260
|
+
|
|
261
|
+
A person files a thought, leaves, comes back later, and finds what became of it unaided — no asking, no database, no one else's help. If they filed it aimed, they can see which goal caught it; if they let it float, they can see that nothing has claimed it yet.
|
|
262
|
+
|
|
263
|
+
This is the exact scenario that failed on 2026-09-19 and produced this whole scope: a thought was filed, it silently became a task under a goal, and the person who filed it could not find it five minutes later. The proof is that this can no longer happen. ${SPEC}`,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
ref: 'R06a', kind: 'verify', discipline: 'engineer', priority: 3, crit: [CRIT.lost], deps: ['R04'],
|
|
267
|
+
title: 'Proof: the one-line path did not get slower',
|
|
268
|
+
desc: `**The guard on spec decision 18**, which is the decision in this scope carrying the most risk.
|
|
269
|
+
|
|
270
|
+
Filing a spark takes the same gestures and the same time it does today, with the Full Idea form present on the same surface. Measure it against today's path, not against an impression.
|
|
271
|
+
|
|
272
|
+
A spark's whole value is that it costs nothing to have. If the fast path picks up an extra step, a scroll, or a decision, this scope will have built something beautiful that people stop using — and they will go back to firing one-liners into Discord. A green build is not the evidence here; a timed comparison is. ${SPEC}`,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
ref: 'R13', kind: 'verify', discipline: 'engineer', priority: 3, crit: [CRIT.fork], deps: ['R10', 'R12'],
|
|
276
|
+
title: 'Proof: a task split twice pays its origin at the decayed rate and appears in the descent',
|
|
277
|
+
desc: `**The live demonstration that \`ideator-tag-survives-the-fork\` is met.**
|
|
278
|
+
|
|
279
|
+
A real thought becomes a real task; that task is split; one of its children is split again; each ships. The origin is paid at the decayed rate at every hop, and the whole chain reads as one descent.
|
|
280
|
+
|
|
281
|
+
This is the criterion everything else stands on. If the tag dies at the first fork, the descent is a link, the size is wrong, and the sky is decoration over a number that does not mean anything. Demonstrate it on real work, not a fixture. ${SPEC}`,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
ref: 'R17', kind: 'feature', discipline: 'engineer', priority: 4, crit: [CRIT.sky], deps: ['R16'],
|
|
285
|
+
title: 'The five states resolve from ledger facts',
|
|
286
|
+
desc: `star / protostar / satellite / remnant / cold body — each DERIVED, none a UI guess.
|
|
287
|
+
|
|
288
|
+
- **star** — caused work that is still alive
|
|
289
|
+
- **protostar** — filed, not yet ignited. Include the well-vs-open-space distinction: caught by a goal and awaiting a nod is a different state from claimed by nothing, and position is what carries it (spec decision, direction doc)
|
|
290
|
+
- **satellite** — folded into another thought; its value counts there
|
|
291
|
+
- **remnant** — it burned, then the project removed it; it keeps its size
|
|
292
|
+
- **cold body** — declined at triage; it never ignited, so it has no size at all
|
|
293
|
+
|
|
294
|
+
Done-when: every state is computed from the ledger, and no surface has to infer one. ${SPEC}`,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
ref: 'R24', kind: 'verify', discipline: 'engineer', priority: 3, crit: [CRIT.full], deps: ['R23'],
|
|
298
|
+
title: 'Proof: a Full Idea ratified as a goal produces a goal carrying its five answers',
|
|
299
|
+
desc: `**The live demonstration that \`ideator-full-idea-shapes-the-space\` is met.**
|
|
300
|
+
|
|
301
|
+
A Full Idea is written through the new five questions, carries a recommendation, goes to the Board Room unchanged, is ratified as a GOAL, and the resulting goal carries those five answers natively — scope from "scope as best as you can here", done-when from "what does a successful implementation look like".
|
|
302
|
+
|
|
303
|
+
Run it end to end through the real Board Room. The point of this criterion is that an ideator's best work becomes the structure other people's thinking lands in; a goal that arrives without its answers has not demonstrated that. ${SPEC}`,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
ref: 'R18', kind: 'feature', discipline: 'ui', priority: 3, crit: [CRIT.sky], deps: ['R17', 'R03'],
|
|
307
|
+
title: 'The sky',
|
|
308
|
+
desc: `The ambient view over the five states: size compressed, brightness live, goals as gravity wells and their criteria as the rings inside them.
|
|
309
|
+
|
|
310
|
+
**It arrives last on purpose.** By this point it draws numbers and states the rest of the work has already produced, which is what keeps it cheap. A galaxy is not trivial, and this scope only earns it because nothing else depends on it.
|
|
311
|
+
|
|
312
|
+
**It is never the only path to anything it shows** (spec decision 11). Every fact in the sky is reachable from the room or the descent.
|
|
313
|
+
|
|
314
|
+
See \`docs/design/mocks/ideator-surface/Sky.dc.html\`, and \`docs/design/ideator-surface-direction.md\` for why the project's own design system — a black void and one accent that lights only what is live — already supplies this. ${SPEC}`,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
ref: 'R19', kind: 'verify', discipline: 'engineer', priority: 3, crit: [CRIT.sky], deps: ['R18'],
|
|
318
|
+
title: 'Proof: a large dead thought renders large and dead',
|
|
319
|
+
desc: `**The live demonstration that \`ideator-sky-tells-the-truth\` is met**, and the single scenario this criterion exists for.
|
|
320
|
+
|
|
321
|
+
A thought that caused substantial work which the project has since removed shows at **full size and zero brightness**, and is distinguishable at a glance from a thought that never caused anything at all.
|
|
322
|
+
|
|
323
|
+
That distinction is the whole reason size and brightness are two channels instead of one number. If those two render alike, the criterion is not met however green the build is. ${SPEC}`,
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
async function main() {
|
|
328
|
+
const ids = {};
|
|
329
|
+
const criterionIdListsPerTask = [];
|
|
330
|
+
|
|
331
|
+
// BASELINE. verifySeedCriteriaLinks unions the plan's slugs with EVERY criterion
|
|
332
|
+
// the goal carries, so a criterion this seed does not touch is compared against an
|
|
333
|
+
// expected zero. Goal 1000095 already held six criteria with live task links before
|
|
334
|
+
// this seed existed, so an unbaselined check would report six false mismatches and
|
|
335
|
+
// exit(1) on a correct run. Snapshot the untouched criteria's real counts HERE,
|
|
336
|
+
// before anything is written, and fold them into the plan — the check then still
|
|
337
|
+
// catches any drift on the four this seed owns, which is what it is for.
|
|
338
|
+
const OURS = new Set(Object.values(CRIT));
|
|
339
|
+
const baseline = {};
|
|
340
|
+
const pre = await api('GET', `/goals/${GOAL}`);
|
|
341
|
+
if (!pre.ok || !pre.json || !Array.isArray(pre.json.criteria)) {
|
|
342
|
+
console.error(`Could not read goal ${GOAL} to baseline its existing criteria (status ${pre.status}). Refusing to seed blind.`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
for (const row of pre.json.criteria) {
|
|
346
|
+
if (!OURS.has(row.criterion_id)) baseline[row.criterion_id] = Number(row.linked_tasks) || 0;
|
|
347
|
+
}
|
|
348
|
+
console.log(`Baselined ${Object.keys(baseline).length} pre-existing criteria on goal ${GOAL}.`);
|
|
349
|
+
|
|
350
|
+
console.log(`Seeding ${TASKS.length} tasks into goal ${GOAL} (${VER})…\n`);
|
|
351
|
+
|
|
352
|
+
for (let i = 0; i < TASKS.length; i++) {
|
|
353
|
+
const t = TASKS[i];
|
|
354
|
+
const rank = String(i + 1).padStart(2, '0');
|
|
355
|
+
const title = `BV2.R${rank} — ${t.title}`;
|
|
356
|
+
const r = await api('POST', '/tasks', {
|
|
357
|
+
version_id: VER,
|
|
358
|
+
goal_id: GOAL,
|
|
359
|
+
title,
|
|
360
|
+
description: t.desc,
|
|
361
|
+
kind: t.kind,
|
|
362
|
+
discipline: t.discipline,
|
|
363
|
+
priority: t.priority,
|
|
364
|
+
criterion_ids: t.crit,
|
|
365
|
+
source: SRC,
|
|
366
|
+
source_ref: `${REF_PREFIX}-${t.ref}`,
|
|
367
|
+
});
|
|
368
|
+
if (!r.ok) {
|
|
369
|
+
console.log(` ${t.ref} FAILED (${r.status}): ${JSON.stringify(r.json).slice(0, 300)}`);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
ids[t.ref] = r.json.task.id;
|
|
373
|
+
criterionIdListsPerTask.push(t.crit);
|
|
374
|
+
console.log(` ${t.ref} → ${r.json.task.id} ${title.slice(0, 74)}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.log('\nWiring dependencies…');
|
|
378
|
+
for (const t of TASKS) {
|
|
379
|
+
if (!t.deps || !t.deps.length) continue;
|
|
380
|
+
const id = ids[t.ref];
|
|
381
|
+
if (!id) { console.log(` ${t.ref}: skipped, task was not created`); continue; }
|
|
382
|
+
const depIds = t.deps.map((d) => ids[d]).filter(Boolean);
|
|
383
|
+
if (depIds.length !== t.deps.length) {
|
|
384
|
+
console.log(` ${t.ref}: WARNING — ${t.deps.length - depIds.length} dep(s) unresolved`);
|
|
385
|
+
}
|
|
386
|
+
if (!depIds.length) continue;
|
|
387
|
+
const r = await api('POST', `/tasks/${id}/dependencies`, { depends_on_task_ids: depIds });
|
|
388
|
+
console.log(` ${t.ref} depends on ${t.deps.join(', ')} → ${r.ok ? 'OK' : `FAILED ${r.status} ${JSON.stringify(r.json).slice(0, 180)}`}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
console.log('');
|
|
392
|
+
await verifySeedCriteriaLinks({
|
|
393
|
+
goalId: GOAL,
|
|
394
|
+
expectedCounts: { ...baseline, ...expectedCriterionCounts(criterionIdListsPerTask) },
|
|
395
|
+
fetchGoal: (gid) => api('GET', `/goals/${gid}`),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
main().catch((e) => { console.error(e); process.exit(1); });
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.897'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -38,6 +38,24 @@ test('a version id is NOT counted — it is a row key, not vocabulary', () => {
|
|
|
38
38
|
assert.equal(scan.countInText(`${LITERAL}-V3 and the ${LITERAL} acronym`), 1);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
test('an encoded blob is NOT counted — a chance run inside base64 is not vocabulary', () => {
|
|
42
|
+
// A committed design mock embeds its own page content as one base64 payload,
|
|
43
|
+
// and three letters turn up inside a large blob by chance. Five did, which
|
|
44
|
+
// was enough to red the ratchet on a change that introduced no literal at all.
|
|
45
|
+
const blob = `AAAA${LITERAL}BBBB`.repeat(40); // > 200 unbroken base64 chars
|
|
46
|
+
assert.ok(blob.length > 200, `the fixture must exceed the threshold, got ${blob.length}`);
|
|
47
|
+
assert.equal(scan.countInText(blob), 0);
|
|
48
|
+
|
|
49
|
+
// …and the strip must not blind the detector to a REAL literal beside it:
|
|
50
|
+
// the same failure mode the version-id case above guards against.
|
|
51
|
+
assert.equal(scan.countInText(`the ${LITERAL} acronym, then ${blob}`), 1);
|
|
52
|
+
|
|
53
|
+
// A SHORT base64-ish run is still counted. The threshold is what keeps this
|
|
54
|
+
// safe, so a change that lowers it has to fail here rather than silently
|
|
55
|
+
// blinding the gate to ordinary identifiers.
|
|
56
|
+
assert.equal(scan.countInText(`AAAA${LITERAL}BBBB`), 1);
|
|
57
|
+
});
|
|
58
|
+
|
|
41
59
|
test('the frozen record is excluded — an ADR or session log may say the old name', () => {
|
|
42
60
|
for (const p of [
|
|
43
61
|
'docs/adr/0064-rename.md',
|