@bongos/core 1.19.817 → 1.19.818
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 +22 -17
- package/docs/adr/0298-the-reader-lens-is-advisory-and-waits-on-a-voice-guide.md +121 -0
- package/docs/adr/README.md +1 -0
- package/docs/module-api-changelog.md +3 -1
- package/modules/lifecycle/cascade.js +18 -5
- package/modules/lifecycle/tests/cascade.mjs +11 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/linkify-refs.js +9 -1
- package/scripts/gds/run-unit-tests.js +8 -1
- package/src/module-api.js +1 -1
- package/tests/linkify_refs.mjs +17 -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.818",
|
|
6
|
+
"core_contract": "1.19.818",
|
|
7
|
+
"source_commit": "7f98428f5e6532100482d729716d92638bee2eb5",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-17T08:
|
|
9
|
+
"built_at": "2026-09-17T08:43:53.963Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 490,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
14
|
"functional_verbatim": 2217,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2733,
|
|
20
|
+
"tree_sha256": "35b8712820d4287686e4a01a9a497c386fd32f24a5109b691a39892d4aeaa964",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -1974,10 +1974,15 @@
|
|
|
1974
1974
|
"mode": "0000644",
|
|
1975
1975
|
"sha256": "4e6d5268c58e905c86acc1bbd4c66778e3695ecd37cbc1c9e0f1448cd67f26a9"
|
|
1976
1976
|
},
|
|
1977
|
+
{
|
|
1978
|
+
"path": "docs/adr/0298-the-reader-lens-is-advisory-and-waits-on-a-voice-guide.md",
|
|
1979
|
+
"mode": "0000644",
|
|
1980
|
+
"sha256": "8fcac086f276e57b59ba635cdf34600bf78bfb69d80177d43065dfc6776b0611"
|
|
1981
|
+
},
|
|
1977
1982
|
{
|
|
1978
1983
|
"path": "docs/adr/README.md",
|
|
1979
1984
|
"mode": "0000644",
|
|
1980
|
-
"sha256": "
|
|
1985
|
+
"sha256": "14c4e2e04bba4a0438375364a88e41eca44983ab0fb2c3ecd13bf0190f0ad151"
|
|
1981
1986
|
},
|
|
1982
1987
|
{
|
|
1983
1988
|
"path": "docs/api-reference.md",
|
|
@@ -2542,7 +2547,7 @@
|
|
|
2542
2547
|
{
|
|
2543
2548
|
"path": "docs/module-api-changelog.md",
|
|
2544
2549
|
"mode": "0000644",
|
|
2545
|
-
"sha256": "
|
|
2550
|
+
"sha256": "0b561d6587c66805edf357a97265aa313ab4cf1610e23c63efbb30fc0f1fdf78"
|
|
2546
2551
|
},
|
|
2547
2552
|
{
|
|
2548
2553
|
"path": "docs/modules-contract.md",
|
|
@@ -5497,7 +5502,7 @@
|
|
|
5497
5502
|
{
|
|
5498
5503
|
"path": "modules/lifecycle/cascade.js",
|
|
5499
5504
|
"mode": "0000644",
|
|
5500
|
-
"sha256": "
|
|
5505
|
+
"sha256": "acc9cf557aabc3fe10efc8c7272162cf288428e8077a24f5df06648e30881337"
|
|
5501
5506
|
},
|
|
5502
5507
|
{
|
|
5503
5508
|
"path": "modules/lifecycle/claim-eligibility.js",
|
|
@@ -5917,7 +5922,7 @@
|
|
|
5917
5922
|
{
|
|
5918
5923
|
"path": "modules/lifecycle/tests/cascade.mjs",
|
|
5919
5924
|
"mode": "0000644",
|
|
5920
|
-
"sha256": "
|
|
5925
|
+
"sha256": "0734ad734ac5ef05c86fa81493c12fed08ffe421781d9ceeebe413c876bf9cf1"
|
|
5921
5926
|
},
|
|
5922
5927
|
{
|
|
5923
5928
|
"path": "modules/lifecycle/version-autoclose.js",
|
|
@@ -7797,12 +7802,12 @@
|
|
|
7797
7802
|
{
|
|
7798
7803
|
"path": "package-lock.json",
|
|
7799
7804
|
"mode": "0000644",
|
|
7800
|
-
"sha256": "
|
|
7805
|
+
"sha256": "c314948249345b2ef9e9c43e7dc32287f3901882cbc0c4d51a475d568ed0fb76"
|
|
7801
7806
|
},
|
|
7802
7807
|
{
|
|
7803
7808
|
"path": "package.json",
|
|
7804
7809
|
"mode": "0000644",
|
|
7805
|
-
"sha256": "
|
|
7810
|
+
"sha256": "8be1abd0cb53633278698edda8b8b78edf57224e89eb3b08806ede49014dab6d"
|
|
7806
7811
|
},
|
|
7807
7812
|
{
|
|
7808
7813
|
"path": "public-docs/index.html",
|
|
@@ -8512,7 +8517,7 @@
|
|
|
8512
8517
|
{
|
|
8513
8518
|
"path": "scripts/gds/linkify-refs.js",
|
|
8514
8519
|
"mode": "0000644",
|
|
8515
|
-
"sha256": "
|
|
8520
|
+
"sha256": "16e9c92e2466f92de1339eac911193807678c4c7d2c6b837ecd1195c102ebf56"
|
|
8516
8521
|
},
|
|
8517
8522
|
{
|
|
8518
8523
|
"path": "scripts/gds/lint-infra-exec.js",
|
|
@@ -8827,7 +8832,7 @@
|
|
|
8827
8832
|
{
|
|
8828
8833
|
"path": "scripts/gds/run-unit-tests.js",
|
|
8829
8834
|
"mode": "0000644",
|
|
8830
|
-
"sha256": "
|
|
8835
|
+
"sha256": "be80f6ea38abdbc71f5f718b51db9a54b931745859e885ea1a97ff9465a81596"
|
|
8831
8836
|
},
|
|
8832
8837
|
{
|
|
8833
8838
|
"path": "scripts/gds/sandbox-stage.js",
|
|
@@ -9692,7 +9697,7 @@
|
|
|
9692
9697
|
{
|
|
9693
9698
|
"path": "src/module-api.js",
|
|
9694
9699
|
"mode": "0000644",
|
|
9695
|
-
"sha256": "
|
|
9700
|
+
"sha256": "d26e3a559beac4c6431a5cf5476c7a0976798ec8b9174814c06f7d64fd1f6f91"
|
|
9696
9701
|
},
|
|
9697
9702
|
{
|
|
9698
9703
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11867,7 +11872,7 @@
|
|
|
11867
11872
|
{
|
|
11868
11873
|
"path": "tests/linkify_refs.mjs",
|
|
11869
11874
|
"mode": "0000644",
|
|
11870
|
-
"sha256": "
|
|
11875
|
+
"sha256": "d072512abdd464716a6880a528da08bc3abb06fd7c69d59d075a699c665229a8"
|
|
11871
11876
|
},
|
|
11872
11877
|
{
|
|
11873
11878
|
"path": "tests/live_channel.mjs",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# ADR 0298 — The grader gets a reader lens: advisory, scoped to copy-bearing diffs, and it waits on a written voice
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · **Date:** 2026-09-17 · **Task:** [task 1003937](https://cloudbongos.com/builders#/task/1003937) (goal 1000095 — Working area 6, the artist experience)
|
|
4
|
+
|
|
5
|
+
**Related:** [ADR 0241](0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md) (the artist holds a deploy, never a ship) · [ADR 0242](0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md) (a cascade is a row, not a runner edit) · [ADR 0024](0024-multi-agent-system-architecture.md) (the panel) · [ADR 0233](0233-a-copy-proposal-is-a-task-carrying-a-patch.md) (there is no live CMS)
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Every artist review the cascade files opens with the same instruction:
|
|
10
|
+
|
|
11
|
+
> STEP 1 — REVIEW THE GRADER, not only the work. Read how the grader judged task N's copy
|
|
12
|
+
> and visuals, and coach it: where was it right, and where did it miss what an artist would
|
|
13
|
+
> catch? … this step is the one that compounds.
|
|
14
|
+
|
|
15
|
+
It cannot be followed, and [task 1003937](https://cloudbongos.com/builders#/task/1003937)
|
|
16
|
+
established why across four reviews worked on 2026-09-15 — 1003621 (of
|
|
17
|
+
[task 1003583](https://cloudbongos.com/builders#/task/1003583)), 1003831 (of
|
|
18
|
+
[task 1003761](https://cloudbongos.com/builders#/task/1003761)), 1003833 (of
|
|
19
|
+
[task 1003828](https://cloudbongos.com/builders#/task/1003828)) and 1003836 (of
|
|
20
|
+
[task 1003829](https://cloudbongos.com/builders#/task/1003829)). The grade itself reads back
|
|
21
|
+
fine; `GET /tasks/:id?include=grade` returns scores, verdict, rubric and per-worker notes. The
|
|
22
|
+
problem is what comes back. `modules/grading/grader-rubric.json` v4.0 runs four lenses:
|
|
23
|
+
|
|
24
|
+
| worker | judges |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `narc` | semantic completion — description vs diff |
|
|
27
|
+
| `quality` | code quality (idiom, dead code, tests) + economy |
|
|
28
|
+
| `hacker` | security red-team |
|
|
29
|
+
| `efficiency` | runtime cost, hot paths |
|
|
30
|
+
|
|
31
|
+
None of them reads a sentence for whether it is true, clear, in the project's voice, or worth
|
|
32
|
+
a person's attention. There is no axis an artist can coach. All four graded tasks passed, and
|
|
33
|
+
all four deserved to pass on the axes that were measured — the gate is not too lenient, it is
|
|
34
|
+
not pointed at this.
|
|
35
|
+
|
|
36
|
+
**One partial mechanism already exists, and its limit is the shape of the gap.**
|
|
37
|
+
`modules/grading/grader-prompt.js` injects the craft's written responsibility into the panel
|
|
38
|
+
prompt — *"No slop in the appearance and text of the project; the story, emotion and ideology
|
|
39
|
+
of the project are communicated effectively; project purpose and gravitas are upheld"* — but
|
|
40
|
+
only when `task.discipline === 'artist'`. All four graded tasks were engineer ships. So the
|
|
41
|
+
unjudged case is exactly **copy shipped by someone whose craft is not the copy**, which is
|
|
42
|
+
most of it.
|
|
43
|
+
|
|
44
|
+
## Decision
|
|
45
|
+
|
|
46
|
+
A fifth panel worker — the **reader** — scoring a copy/voice axis. Three things were open;
|
|
47
|
+
the owner settled all three on 2026-09-17.
|
|
48
|
+
|
|
49
|
+
### 1. It runs on copy-bearing diffs only, not on every ship
|
|
50
|
+
|
|
51
|
+
The panel already runs four subagents per ship. A fifth on every ship is a standing spend
|
|
52
|
+
increase on the large majority of ships that change no visible string.
|
|
53
|
+
|
|
54
|
+
The predicate is not new work: `hasCopyOrVisualWork(task)` in `modules/lifecycle/cascade.js`
|
|
55
|
+
already decides, for the artist-review cascade, whether a shipped task put copy or visuals in
|
|
56
|
+
front of a person — `source === 'copy-proposal'`, `discipline === 'artist'`, or a
|
|
57
|
+
`module_key` in the explicit `COPY_VISUAL_MODULES` set. **The reader lens keys off the same
|
|
58
|
+
predicate.** One definition of "this ship is copy-bearing", used by both the after-the-fact
|
|
59
|
+
review and the at-ship lens, so the two can never disagree about what they cover. Widening
|
|
60
|
+
that set widens both at once, deliberately.
|
|
61
|
+
|
|
62
|
+
Where the predicate says no, the panel is exactly what it is today and costs exactly what it
|
|
63
|
+
costs today.
|
|
64
|
+
|
|
65
|
+
### 2. It is advisory — findings, never a verdict
|
|
66
|
+
|
|
67
|
+
The reader records `issues[]` and a `copy_score`. It **cannot** verdict `fail`, and it cannot
|
|
68
|
+
drag the panel to `concerns`.
|
|
69
|
+
|
|
70
|
+
This is not caution, it is [ADR 0241](0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md)
|
|
71
|
+
applied as written. That ADR settled that an artist's judgement may hold a **deploy**, decided
|
|
72
|
+
per project, and never a **builder's ship** — after ADR 0162 retired review gates for stranding
|
|
73
|
+
graded-PASS work. A reader worker that could verdict `fail` would put an artist-shaped
|
|
74
|
+
judgement back on the ship path, which is the precise thing 0241 removed. Gating it would
|
|
75
|
+
require amending 0241 first, and nothing here justifies that.
|
|
76
|
+
|
|
77
|
+
Advisory is also what makes STEP 1 answerable: the artist's review reads the reader's findings
|
|
78
|
+
and coaches *them*, which is a real conversation with a real counterpart, rather than coaching
|
|
79
|
+
four lenses that were never looking.
|
|
80
|
+
|
|
81
|
+
### 3. It waits on a written voice, and does not ship before one exists
|
|
82
|
+
|
|
83
|
+
A copy lens needs a standard to judge against or it is a second model's taste wearing a
|
|
84
|
+
score. The instance's voice is, today, almost entirely unwritten:
|
|
85
|
+
|
|
86
|
+
| source | what it actually carries |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `DESIGN.md` | one hard copy rule — no em/en dash in visible copy, no emoji |
|
|
89
|
+
| `config/branding.neutral.json` | a tagline and a handful of fixed strings |
|
|
90
|
+
| `docs/copy-registry.json` | which 1,860 strings exist — not what good is |
|
|
91
|
+
| `src/role-responsibilities.js` | one sentence, and it only reaches artist-discipline ships |
|
|
92
|
+
|
|
93
|
+
So the build order is fixed: **voice guide first, reader second.** The reader worker is not
|
|
94
|
+
started until the guide is written, and the guide is the artist's own work, not an agent's
|
|
95
|
+
draft of it. A lens built against the four rows above would be recording opinions and calling
|
|
96
|
+
them findings, and that residue would be worse than the silence it replaced — a score nobody
|
|
97
|
+
can appeal to a standard is harder to remove than a missing score.
|
|
98
|
+
|
|
99
|
+
### 4. Until the reader exists, the cascade brief says something an artist can do
|
|
100
|
+
|
|
101
|
+
STEP 1 stays — the instinct behind it is right and this ADR is the proof that it compounds —
|
|
102
|
+
but it stops asking for coaching that has no recipient. In the interim it asks the artist to
|
|
103
|
+
record *where the grade missed what an artist would catch*, naming that the record is the
|
|
104
|
+
evidence this lens is being built from. That is followable today, it produces exactly the
|
|
105
|
+
corpus the voice guide needs, and it converts back into true coaching the moment the reader
|
|
106
|
+
lands.
|
|
107
|
+
|
|
108
|
+
## Consequences
|
|
109
|
+
|
|
110
|
+
- `modules/lifecycle/cascade.js` — STEP 1 rewritten to an instruction that can be carried out,
|
|
111
|
+
pointing at this ADR. No change to the predicate, the trigger, the anti-recursion guard, or
|
|
112
|
+
the fact that the review blocks nothing.
|
|
113
|
+
- Two follow-up tasks are filed at `backlog`, in order, with the dependency edge wired:
|
|
114
|
+
[task 1003976](https://cloudbongos.com/builders#/task/1003976) — the **voice guide** (artist) —
|
|
115
|
+
then [task 1003977](https://cloudbongos.com/builders#/task/1003977) — the **reader worker**
|
|
116
|
+
(engineer, `depends_on` 1003976, so it cannot reach `ready` until the guide ships).
|
|
117
|
+
Neither is started by this ADR.
|
|
118
|
+
- The rubric, the active panel, and every existing worker are untouched. No ship's cost or
|
|
119
|
+
verdict changes as of this decision.
|
|
120
|
+
- If the voice guide is never written, the reader is never built, and STEP 1 is still honest —
|
|
121
|
+
which is the outcome this ADR is careful to leave standing rather than forcing.
|
package/docs/adr/README.md
CHANGED
|
@@ -389,3 +389,4 @@ This keeps the decision history honest and traceable.
|
|
|
389
389
|
| 0295 | [**The registry is the witness the publish lane cannot be** ([task 1003916](https://cloudbongos.com/builders#/task/1003916), goal 1000090). On 2026-09-13 eight publish runs failed over three hours and NOTHING reported it; it was found by a person idly reading the npm version list. Task 1003914 fixed the lossiness and left the silence, which is structural: a failed run is one red row nobody watches, `main` self-heals on the next merge and **erases the evidence**, and the lane's own verdict step writes to a job summary no one reads. Same shape as the nightly upgrade sweep that died reporting `failed=0`. **Decision: a separate reader compares what the lane ANNOUNCED (release commits) against what the registry HOLDS**; the difference is a phantom. The publisher infers from git because OIDC trusted publishing carries no read credential — **that never bound a reader**, and since task 1003872 the package is public, so an anonymous read needs none. Keeping witness and publisher separate IS the point. Daily not hourly (billable minutes, against a failure measured in hours), cadence pinned to the cron by `routine-schedule.js`; deliberately NOT on the session-start path (a third-party call behind a hook timeout). Healthy days are SILENT in Discord — findings to stdout, the all-clear to stderr — because a channel of reassurances stops being read. Two false-alarm sources, each guarded: a closed incident is permanent history (a 3-day window, by TIME not commit count, which a release burst would outrun), and **npm acknowledges a publish before its listing shows it** — found by RUNNING the check, which flagged 1.19.794 fifteen minutes after a publish whose log ended `published @bongos/core@1.19.794` and whose per-version endpoint answered 200; hence a 30-minute grace period plus per-version confirmation, with the listing only ever nominating suspects. A failed read throws rather than reading as "nothing missing". Proven by replaying the REAL outage from git + the live registry, not a synthetic failure. Does NOT detect a lane that stopped being triggered.](0295-the-registry-is-the-witness-the-lane-cannot-be.md) | release / observability |
|
|
390
390
|
| 0296 | [**A role speciality is data over three existing surfaces, not a fourth mechanism** ([task 1003855](https://cloudbongos.com/builders#/task/1003855), goal 1000095). A craft says HOW you work; nothing said what you work ON. A speciality is a named bundle of knowledge, skills and an interaction contract sitting under a craft — an ideator with an Economics speciality. **Decision: it is DATA, not an ADR 0083 code module** — a subject-matter expert authors one, and requiring them to ship a package puts the mechanism out of reach of exactly the person it exists for. It layers over three surfaces that already exist (discipline-modes.json, the `behavior_prefs.interaction` contract path, the manifest contribution seam); only the knowledge bundle is new. The contract **rides the existing GET /me -> Conductor injection path rather than adding a second one**, and is CRAFT-SCOPED: the hook injects only the contract matching the claim in hand (which needed `t.discipline` on the active-claims read), and with no claim there is no craft and **nothing is injected** — silence beats guessing which expertise applies. Training is two-fold per the owner's 2026-09-17 ruling: documents they supplied, plus learnings captured from sessions worked in it (pointing at existing `learnings` rows, never a second corpus). The corpus is **inventoried, never inlined** — a document may run to 100k chars — so `speciality_documents` joins the recall ingester, because otherwise the contract's "search it with /recall" is a lie whose failure is silent. **`sellable` is recorded with no storefront behind it**: ADR 0107 defers the marketplace and explicitly rejected building one, so the flag captures the owner's decision when they make it and grants nothing — a deliberate partial implementation, recorded rather than silently made. Index scoping errs toward UNDER-sharing: only a public speciality's documents are corpus-wide, because `project` names a set the index cannot express.](0296-a-role-speciality-is-data-over-three-existing-surfaces.md) | roles / modules |
|
|
391
391
|
| 0297 | [**A task’s creator may retire it, and that is the ownership axis’s first route** ([task 1002443](https://cloudbongos.com/builders#/task/1002443), goal 1000095 — owner request 2026-07-25: *"the creator of that task should be able to abandon that task directly inside of the UI"*). Attribution itself shipped earlier and elsewhere (`core_227`, [task 1003278](https://cloudbongos.com/builders#/task/1003278)); what had not moved is that **retiring a task was a rank power and nothing else** — `task.abandon` floors at `metic` (ADR 0152), so a sub-Metic builder could file a task and then could not withdraw their own proposal. The model already had the vocabulary and had never used it: ADR 0151 §3 defines an **ownership axis** where a `scope:'own'` key authorizes nothing until a handler supplies the row's owner, R100 made `requirePermission` refuse such a key outright so `.own` can never degrade into `.any`, and it recorded that **zero call sites passed one**. This is that axis’s first real consumer. **Decision: two authorities, only one rank-shaped** — `task.abandon` (metic, any task) OR new `task.abandon.own` (xenos, a task you created, while it is still backlog/ready/blocked). Route becomes `requireBuilder` + an in-handler decision (the `POST /goals/:id/members` shape), pin `metic+archon`→`any-builder` so it now catches an accidental **tightening** as loudly as a gate removal. **The two facts resolve in two places on purpose:** the RANK fact is request-scoped and resolves before the transaction (uncached, ADR 0016); the OWNER fact is a column that can change under us, so `created_by` joins `abandonTask`’s existing `FOR UPDATE` SELECT and a new optional `authorize` callback decides under that lock. Reading `created_by` in the route would be a TOCTOU window; resolving permissions inside the transaction would hold a row lock across a second pool acquire. The decision is a PURE module (`abandon-authz.js`, the `goal-route-authz.js` precedent) so every branch is pinned DB-free and the hall hint and server verdict provably read one rule. **A creator’s window stops at the grader** — completed/confirmed stay cross-owner, because past the grader a retire is a ledger correction, not an author changing their mind; nothing is subtracted from `task.abandon`, pinned by a test. `counterpart: ['task.abandon']` is declared not derived, verb `act` not `manage` — `manage` would silently dominate every future narrower `task.*.own` key (the verb-blind escalation task 1003195 closed). **A null creator belongs to nobody and that is load-bearing:** ~1,357 pre-`core_227` tasks plus seed/restock/discord vectors are unattributed, so the owner comparison is the government resolver’s own — injected, never re-implemented, since a duplicated ownership comparison is how one quietly widens — and one slip would open every historical task to a xenos grant at once. Fail-closed throughout: a missing `government` port, a throwing resolver, and anything but an explicit `{ok:true}` all deny, and `authorize` runs BEFORE the idempotent already-abandoned return and the shipped/active-claim guards so an unauthorized caller cannot probe a row through a 200. No guard removed. Does NOT widen edit/promote/dependency (still Metic), does not touch `task.merge`, and does not backfill attribution — the control simply does not appear on tasks predating `core_227`.](0297-a-tasks-creator-may-retire-it.md) | authority / lifecycle / ownership |
|
|
392
|
+
| 0298 | [**The grader gets a reader lens: advisory, scoped to copy-bearing diffs, and it waits on a written voice** ([task 1003937](https://cloudbongos.com/builders#/task/1003937), goal 1000095 — Working area 6; owner decision 2026-09-17). Every artist review the cascade files opened with *“REVIEW THE GRADER … coach it”*, and it could not be followed: the v4 panel runs narc, quality, hacker and efficiency — completion, code quality, security, runtime cost — and not one of them reads a sentence for whether it is true, clear, in the project’s voice or worth a person’s attention. Four consecutive reviews hit the same wall and all four graded ships deservedly PASSED on the axes that were measured; the gate is not lenient, it is not pointed at this. `grader-prompt.js` already injects the craft’s written responsibility, but only when `discipline === ‘artist’`, so the unjudged case is copy shipped by someone whose craft is not the copy. **Decision: a fifth ADVISORY `reader` worker, on three settled terms.** It runs only where `modules/lifecycle/cascade.js`’s existing `hasCopyOrVisualWork` says the ship is copy-bearing — the same predicate the after-the-fact review uses, so the two can never disagree about coverage, and every other ship costs exactly what it costs today. It records `issues[]` and a `copy_score` and can never verdict `fail` or drag the panel to `concerns`, because [ADR 0241](0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md) put the artist’s judgement on the DEPLOY and off the ship path; gating would need that ADR amended first. And it is NOT BUILT until the instance writes a voice guide — today the whole written standard is one dash rule in `DESIGN.md`, a tagline, a registry of which 1,860 strings exist, and one responsibility sentence, so a lens built now would record taste and call it a finding. Until the reader exists, STEP 1 asks the artist to WRITE DOWN what the grade could not see rather than to coach a lens that is not listening — followable today, and the corpus both the guide and the lens get written from. Nothing in the rubric, the active panel or any existing worker changes.](0298-the-reader-lens-is-advisory-and-waits-on-a-voice-guide.md) | grading / the artist’s loop / voice |
|
|
@@ -2107,6 +2107,8 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2107
2107
|
1.19.816 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2108
2108
|
landed since 1.19.815 with no explicit bump. run 35198566737. (task 1002620)
|
|
2109
2109
|
1.19.817 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2110
|
-
landed since 1.19.816 with no explicit bump. run
|
|
2110
|
+
landed since 1.19.816 with no explicit bump. run 35200401779. (task 1002620)
|
|
2111
|
+
1.19.818 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2112
|
+
landed since 1.19.817 with no explicit bump. run 35201213516. (task 1002620)
|
|
2111
2113
|
---------------------------------------------------------------------------
|
|
2112
2114
|
```
|
|
@@ -131,18 +131,31 @@ function hasCopyOrVisualWork(task) {
|
|
|
131
131
|
return false;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
// The two-step artist review body. STEP 1
|
|
135
|
-
//
|
|
136
|
-
//
|
|
134
|
+
// The two-step artist review body. STEP 1 records where the grade was blind;
|
|
135
|
+
// STEP 2 remakes the copy/visuals with both routes explicitly offered — R04's
|
|
136
|
+
// brief, verbatim in intent.
|
|
137
|
+
//
|
|
138
|
+
// STEP 1 used to say "coach the grader", and for as long as that was the wording
|
|
139
|
+
// it asked for something no artist could do (task 1003937, ADR 0298). The panel's
|
|
140
|
+
// four lenses — narc, quality, hacker, efficiency — read completion, code quality,
|
|
141
|
+
// security and runtime cost; not one of them reads a sentence, so there was no
|
|
142
|
+
// counterpart to coach and four consecutive reviews found the same wall. The
|
|
143
|
+
// ADR's decision is a fifth ADVISORY 'reader' lens, scoped to copy-bearing diffs
|
|
144
|
+
// by this file's own hasCopyOrVisualWork, built only after the instance writes a
|
|
145
|
+
// voice guide to judge against. Until it exists the step asks for the RECORD
|
|
146
|
+
// instead of the coaching: followable today, and it is the corpus both the guide
|
|
147
|
+
// and the lens will be written from. When the reader lands, this reverts to
|
|
148
|
+
// coaching a lens that is actually listening — and the wording is the signal for
|
|
149
|
+
// which of those two worlds we are in, so do not restore the old sentence early.
|
|
137
150
|
function artistReviewBody(task) {
|
|
138
151
|
return [
|
|
139
152
|
`This is an ARTIST REVIEW, generated automatically because task ${task.id} put copy or visuals in front of a person. It is a REQUEST, not a gate — it blocks nothing that shipped, and triage may dismiss it.`,
|
|
140
153
|
'',
|
|
141
|
-
`STEP 1 —
|
|
154
|
+
`STEP 1 — READ THE GRADE, AND WRITE DOWN WHAT IT COULD NOT SEE. Pull how the grader judged task ${task.id} (GET /tasks/${task.id}?include=grade), then record what an artist would have caught and no lens did: slop, an off-voice line, a claim that does not land, a visual that reads as generated. Do not expect to find a copy verdict to argue with — the panel's four lenses read completion, code quality, security and runtime cost, and none of them reads a sentence (ADR 0298). Your record IS the compounding part: it is the evidence the advisory 'reader' lens is being built from, and the corpus the voice guide it waits on will be written against.`,
|
|
142
155
|
'',
|
|
143
156
|
'STEP 2 — REMAKE OR TWEAK the copy/visuals if they need it. Your choice of route, both offered on purpose: OUTSIDE the AI chat (edit the surface directly and land it through the copy desk or the normal pipeline), or WITH it (draft in chat, then land). Either way it lands as a claimed, graded, shipped task like everything else — there is no live CMS (goal 1000074\'s hard rule).',
|
|
144
157
|
'',
|
|
145
|
-
'If the copy and visuals are fine and
|
|
158
|
+
'If the copy and visuals are fine and there was nothing the grade missed, DISMISS this in triage. That is a real, expected outcome — not a failure.',
|
|
146
159
|
].join('\n');
|
|
147
160
|
}
|
|
148
161
|
|
|
@@ -212,7 +212,17 @@ await test('a copy/visual ship generates the two-step artist review', () => {
|
|
|
212
212
|
assert.equal(spec.status, 'backlog', 'a request that triage may dismiss — not claimable, not nagging');
|
|
213
213
|
assert.match(spec.title, /7001/);
|
|
214
214
|
// Both steps, and both routes of step 2, are in the body.
|
|
215
|
-
|
|
215
|
+
//
|
|
216
|
+
// STEP 1's label tracks ADR 0298 (task 1003937). It used to read
|
|
217
|
+
// "REVIEW THE GRADER … coach it", which asked for something no artist could
|
|
218
|
+
// do: the panel's four lenses read completion, code quality, security and
|
|
219
|
+
// runtime cost, so there was no copy verdict to coach. Until the advisory
|
|
220
|
+
// reader worker ships (task 1003977, itself gated on the voice guide in
|
|
221
|
+
// task 1003976) the step asks for the RECORD instead — hence the pin on the
|
|
222
|
+
// new label AND on the ADR reference, which is what stops a well-meaning
|
|
223
|
+
// copy-edit from restoring the unfollowable version without reading why.
|
|
224
|
+
assert.match(spec.description, /STEP 1 — READ THE GRADE, AND WRITE DOWN WHAT IT COULD NOT SEE/);
|
|
225
|
+
assert.match(spec.description, /ADR 0298/, 'the step names the decision that shaped it');
|
|
216
226
|
assert.match(spec.description, /STEP 2 — REMAKE OR TWEAK/);
|
|
217
227
|
assert.match(spec.description, /OUTSIDE the AI chat/);
|
|
218
228
|
assert.match(spec.description, /WITH it/);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.818",
|
|
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.818",
|
|
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.818",
|
|
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",
|
|
@@ -115,6 +115,14 @@ const { API_BASE } = require('./cli-lib');
|
|
|
115
115
|
// the root CLAUDE.md/README.md plus all of docs/** (session-logs included). A
|
|
116
116
|
// "GENERATED FILE" marker means the bare refs reappear on the next regen, so
|
|
117
117
|
// the generator — not its output — is where those get fixed; skip them here.
|
|
118
|
+
// PATHS COME BACK POSIX-STYLE, on every platform (task 1003935). path.join gives
|
|
119
|
+
// OS-native separators, so on Windows this used to return 'docs\\adr\\0001-….md'.
|
|
120
|
+
// Three things read these paths and only one of them tolerates that: fs accepts
|
|
121
|
+
// either separator on Windows, but the --check report PRINTS them (so CI output
|
|
122
|
+
// differed by platform for identical content) and the test that pins recursion
|
|
123
|
+
// into docs/ compares against 'docs/' — which is how a permanently-red case hid
|
|
124
|
+
// for months in a file no lane ran. Normalising here, at the one boundary that
|
|
125
|
+
// produces them, is cheaper than teaching every consumer to be separator-aware.
|
|
118
126
|
function discoverDocs() {
|
|
119
127
|
const out = [];
|
|
120
128
|
const walk = (rel) => {
|
|
@@ -123,7 +131,7 @@ function discoverDocs() {
|
|
|
123
131
|
if (fs.statSync(abs).isDirectory()) {
|
|
124
132
|
for (const name of fs.readdirSync(abs).sort()) walk(path.join(rel, name));
|
|
125
133
|
} else if (rel.endsWith('.md')) {
|
|
126
|
-
out.push(rel);
|
|
134
|
+
out.push(rel.split(path.sep).join('/'));
|
|
127
135
|
}
|
|
128
136
|
};
|
|
129
137
|
for (const root of ['CLAUDE.md', 'README.md', 'docs']) walk(root);
|
|
@@ -131,7 +131,14 @@ const INTEGRATION = new Set([
|
|
|
131
131
|
// the --integration lane never saw them either, because it runs only this set. A
|
|
132
132
|
// test that cannot fail in either lane is a file that looks like a gate.
|
|
133
133
|
'handle_machinery', 'hub_profile_leak', 'profile_fields',
|
|
134
|
-
'linkify_refs'
|
|
134
|
+
// task 1003935: 'linkify_refs' was swept in with the three above without the
|
|
135
|
+
// no-DB check they were being fixed for. It touches no Postgres at all — pure
|
|
136
|
+
// string work plus a filesystem walk — so the DB-free lane skipped it and the
|
|
137
|
+
// --integration lane, which runs only this set, never had a reason to. It ran in
|
|
138
|
+
// NEITHER lane, which is the exact failure the comment above describes, and it
|
|
139
|
+
// is why one permanently-red Windows case sat unnoticed. It is now in the unit
|
|
140
|
+
// gate, where it is the only coverage linkifyText has.
|
|
141
|
+
'memory_isolation',
|
|
135
142
|
'memory_pull', 'needs_rebase', 'parallel-load', 'profile_rollup_consent',
|
|
136
143
|
// task 1003145: whether the PLANNER uses the migration 225 index for the recall
|
|
137
144
|
// freshness probe. The DB-free sibling (recall_corpus_health) pins the query
|
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.818'; // 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');
|
package/tests/linkify_refs.mjs
CHANGED
|
@@ -290,6 +290,23 @@ t('a known idea link is never unwrapped by the past-mislink pass', () => {
|
|
|
290
290
|
assert.equal(runIds(s, ids), s);
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
+
// task 1003935: the separator contract, stated rather than assumed.
|
|
294
|
+
//
|
|
295
|
+
// discoverDocs builds with path.join, so on Windows it returned
|
|
296
|
+
// 'docs\\adr\\0001-….md' and the recursion assertion below — which compares against
|
|
297
|
+
// 'docs/' — was permanently red. Nobody saw it: this file was in run-unit-tests'
|
|
298
|
+
// INTEGRATION exclusion set while touching no database, so it ran in neither lane
|
|
299
|
+
// (fixed in the same task). The assertion was right and the walker was wrong to
|
|
300
|
+
// hand it OS-native paths, so the fix is here at the boundary; this test exists so
|
|
301
|
+
// the next reader does not have to rediscover which of the two was at fault.
|
|
302
|
+
t('discoverDocs returns POSIX-style paths on every platform (task 1003935)', () => {
|
|
303
|
+
const docs = discoverDocs();
|
|
304
|
+
const native = docs.filter((d) => d.includes('\\'));
|
|
305
|
+
assert.equal(native.length, 0,
|
|
306
|
+
`paths must not carry OS-native separators; got ${JSON.stringify(native.slice(0, 3))}`);
|
|
307
|
+
assert.ok(docs.some((d) => d.includes('/')), 'and nested paths must still be nested');
|
|
308
|
+
});
|
|
309
|
+
|
|
293
310
|
t('discoverDocs finds CLAUDE.md, recurses docs/ subdirs (only .md), skips the generated ideas file', () => {
|
|
294
311
|
const docs = discoverDocs();
|
|
295
312
|
assert.ok(docs.includes('CLAUDE.md'), 'includes CLAUDE.md');
|