@bongos/core 1.19.339 → 1.19.341
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 +41 -26
- package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +162 -0
- package/docs/adr/README.md +1 -0
- package/docs/copy-inventory.md +153 -151
- package/docs/copy-registry.json +212 -194
- package/docs/file-map.md +1 -1
- package/docs/recipes/redteam-patrol.md +57 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +18 -3
- package/public-landing/projects.html +191 -51
- package/scripts/gds/control-manifest.js +10 -3
- package/scripts/gds/fitness.js +1 -0
- package/scripts/gds/redteam-patrol.js +78 -0
- package/scripts/gds/routine-schedule.js +231 -0
- package/scripts/gds/run-routine.js +14 -5
- package/src/module-api.js +5 -1
- package/tests/projects_hub.mjs +1 -1
- package/tests/projects_hub_pre_uat.mjs +85 -4
- package/tests/redteam_patrol.mjs +63 -0
- package/tests/routine_schedule.mjs +176 -0
- package/tests/run_routine.mjs +5 -2
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.341",
|
|
6
|
+
"core_contract": "1.19.341",
|
|
7
|
+
"source_commit": "395ad8796967381d04b9f5ad9247da0cbf60d419",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-25T01:
|
|
9
|
+
"built_at": "2026-08-25T01:41:39.768Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 326,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1485,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1833,
|
|
20
|
+
"tree_sha256": "29b405f9216626b6f363e10be67e90f2bde67fdd472151fc4eaf0a6c18384acd",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -1419,10 +1419,15 @@
|
|
|
1419
1419
|
"mode": "0000644",
|
|
1420
1420
|
"sha256": "d59906a447f2459efdef6c3eb97cf8629813ec74776ead700017f02ef76aed9e"
|
|
1421
1421
|
},
|
|
1422
|
+
{
|
|
1423
|
+
"path": "docs/adr/0188-patrol-runs-on-ci-not-the-server.md",
|
|
1424
|
+
"mode": "0000644",
|
|
1425
|
+
"sha256": "4feacf7bc809cdff1a23a1ef5b57d5d28aa448b80bd1ad24fc70860c0fe6739f"
|
|
1426
|
+
},
|
|
1422
1427
|
{
|
|
1423
1428
|
"path": "docs/adr/README.md",
|
|
1424
1429
|
"mode": "0000644",
|
|
1425
|
-
"sha256": "
|
|
1430
|
+
"sha256": "4c736c015e635ed0b4e78445f35acbd8b84a14708634e5caf4a957357b1e22b9"
|
|
1426
1431
|
},
|
|
1427
1432
|
{
|
|
1428
1433
|
"path": "docs/api-reference.md",
|
|
@@ -1457,12 +1462,12 @@
|
|
|
1457
1462
|
{
|
|
1458
1463
|
"path": "docs/copy-inventory.md",
|
|
1459
1464
|
"mode": "0000644",
|
|
1460
|
-
"sha256": "
|
|
1465
|
+
"sha256": "a47b42bd38656c5aa947d278ecf4b82cdf7113d94ac6c6796f8b7ecada6030e5"
|
|
1461
1466
|
},
|
|
1462
1467
|
{
|
|
1463
1468
|
"path": "docs/copy-registry.json",
|
|
1464
1469
|
"mode": "0000644",
|
|
1465
|
-
"sha256": "
|
|
1470
|
+
"sha256": "11d91419272c08d96af33fc4a9df963b3f4c249874e2bfebd32396a40ac275e5"
|
|
1466
1471
|
},
|
|
1467
1472
|
{
|
|
1468
1473
|
"path": "docs/design-contract.md",
|
|
@@ -1572,7 +1577,7 @@
|
|
|
1572
1577
|
{
|
|
1573
1578
|
"path": "docs/file-map.md",
|
|
1574
1579
|
"mode": "0000644",
|
|
1575
|
-
"sha256": "
|
|
1580
|
+
"sha256": "bbe32c152ba31a68953f318f9a94e010252e70a6e076d64d7dca820f5c19777b"
|
|
1576
1581
|
},
|
|
1577
1582
|
{
|
|
1578
1583
|
"path": "docs/handoff-template.md",
|
|
@@ -1772,7 +1777,7 @@
|
|
|
1772
1777
|
{
|
|
1773
1778
|
"path": "docs/recipes/redteam-patrol.md",
|
|
1774
1779
|
"mode": "0000644",
|
|
1775
|
-
"sha256": "
|
|
1780
|
+
"sha256": "656f30f568a56111ce6c2db8f3d61b0f9df3b168f41388b953a4552144579c94"
|
|
1776
1781
|
},
|
|
1777
1782
|
{
|
|
1778
1783
|
"path": "docs/recipes/search-index-ingestion.md",
|
|
@@ -4727,12 +4732,12 @@
|
|
|
4727
4732
|
{
|
|
4728
4733
|
"path": "package-lock.json",
|
|
4729
4734
|
"mode": "0000644",
|
|
4730
|
-
"sha256": "
|
|
4735
|
+
"sha256": "7c3952267fd38da7241791d8f5c4e7a2d8226d4cf8d30473fd6fe56ca000a4af"
|
|
4731
4736
|
},
|
|
4732
4737
|
{
|
|
4733
4738
|
"path": "package.json",
|
|
4734
4739
|
"mode": "0000644",
|
|
4735
|
-
"sha256": "
|
|
4740
|
+
"sha256": "d2ed11ab8e90eb8293778ce19fe921737158c0ab9d8063a9842383b1cfc59e94"
|
|
4736
4741
|
},
|
|
4737
4742
|
{
|
|
4738
4743
|
"path": "public-docs/index.html",
|
|
@@ -4972,7 +4977,7 @@
|
|
|
4972
4977
|
{
|
|
4973
4978
|
"path": "public-landing/assets/cosmos.css",
|
|
4974
4979
|
"mode": "0000644",
|
|
4975
|
-
"sha256": "
|
|
4980
|
+
"sha256": "0853f9974cf971b28022bbbaef2e5bc13ea2ae50363e6141b0d7bb4638e4bdf8"
|
|
4976
4981
|
},
|
|
4977
4982
|
{
|
|
4978
4983
|
"path": "public-landing/assets/verro-medallion.png",
|
|
@@ -4992,7 +4997,7 @@
|
|
|
4992
4997
|
{
|
|
4993
4998
|
"path": "public-landing/projects.html",
|
|
4994
4999
|
"mode": "0000644",
|
|
4995
|
-
"sha256": "
|
|
5000
|
+
"sha256": "208f23e9909da075d0f2bb64732ef34253421615fc6c7c6142eb3605637821f3"
|
|
4996
5001
|
},
|
|
4997
5002
|
{
|
|
4998
5003
|
"path": "public-landing/world/assets/clouds/cloud-island-large.png",
|
|
@@ -5262,7 +5267,7 @@
|
|
|
5262
5267
|
{
|
|
5263
5268
|
"path": "scripts/gds/control-manifest.js",
|
|
5264
5269
|
"mode": "0000644",
|
|
5265
|
-
"sha256": "
|
|
5270
|
+
"sha256": "6c9538a110e45a184531e3ddd7725a8307da823411ef71f0d0b575dd3ee9571b"
|
|
5266
5271
|
},
|
|
5267
5272
|
{
|
|
5268
5273
|
"path": "scripts/gds/copy-inventory.js",
|
|
@@ -5407,7 +5412,7 @@
|
|
|
5407
5412
|
{
|
|
5408
5413
|
"path": "scripts/gds/fitness.js",
|
|
5409
5414
|
"mode": "0000644",
|
|
5410
|
-
"sha256": "
|
|
5415
|
+
"sha256": "925006ed968209632b20677d05594158ebc27e65229d96a7d69a53baf93f6ed0"
|
|
5411
5416
|
},
|
|
5412
5417
|
{
|
|
5413
5418
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -5772,7 +5777,7 @@
|
|
|
5772
5777
|
{
|
|
5773
5778
|
"path": "scripts/gds/redteam-patrol.js",
|
|
5774
5779
|
"mode": "0000644",
|
|
5775
|
-
"sha256": "
|
|
5780
|
+
"sha256": "7460ee330a6bc53176a6ab83cb1019d50fe7d872d8a5bf31486e87edc86059af"
|
|
5776
5781
|
},
|
|
5777
5782
|
{
|
|
5778
5783
|
"path": "scripts/gds/regen-instance-docs.js",
|
|
@@ -5799,10 +5804,15 @@
|
|
|
5799
5804
|
"mode": "0000644",
|
|
5800
5805
|
"sha256": "084fa5c4da1e755fa0edde5ec770e5151a9f66a247bca222bc7a2366ccf61748"
|
|
5801
5806
|
},
|
|
5807
|
+
{
|
|
5808
|
+
"path": "scripts/gds/routine-schedule.js",
|
|
5809
|
+
"mode": "0000644",
|
|
5810
|
+
"sha256": "0d75c6b9be5a67738c91e55cda9084e10d7c64aa49c80ce9c0fa4219afcbd14c"
|
|
5811
|
+
},
|
|
5802
5812
|
{
|
|
5803
5813
|
"path": "scripts/gds/run-routine.js",
|
|
5804
5814
|
"mode": "0000644",
|
|
5805
|
-
"sha256": "
|
|
5815
|
+
"sha256": "8b7765f52f4d6e6559a47bbc911bf5edc4acea81a3765b8811be9fd07be8dfb7"
|
|
5806
5816
|
},
|
|
5807
5817
|
{
|
|
5808
5818
|
"path": "scripts/gds/run-unit-tests.js",
|
|
@@ -6557,7 +6567,7 @@
|
|
|
6557
6567
|
{
|
|
6558
6568
|
"path": "src/module-api.js",
|
|
6559
6569
|
"mode": "0000644",
|
|
6560
|
-
"sha256": "
|
|
6570
|
+
"sha256": "96c4cf1842a773983825fb0a8639b3e73eb0a3e7646a38781962530f072f6457"
|
|
6561
6571
|
},
|
|
6562
6572
|
{
|
|
6563
6573
|
"path": "src/module-loader/catalog.js",
|
|
@@ -8422,12 +8432,12 @@
|
|
|
8422
8432
|
{
|
|
8423
8433
|
"path": "tests/projects_hub.mjs",
|
|
8424
8434
|
"mode": "0000644",
|
|
8425
|
-
"sha256": "
|
|
8435
|
+
"sha256": "c6062fe1a27ef5797e82e706e85034570baa933cca1d1db041b40bd3f5517a0b"
|
|
8426
8436
|
},
|
|
8427
8437
|
{
|
|
8428
8438
|
"path": "tests/projects_hub_pre_uat.mjs",
|
|
8429
8439
|
"mode": "0000644",
|
|
8430
|
-
"sha256": "
|
|
8440
|
+
"sha256": "d1f8e7c790b3086e15d727c38738e2ee8a08dc439a5b0d19ec00f988754fd8df"
|
|
8431
8441
|
},
|
|
8432
8442
|
{
|
|
8433
8443
|
"path": "tests/promote_goal_id.mjs",
|
|
@@ -8567,7 +8577,7 @@
|
|
|
8567
8577
|
{
|
|
8568
8578
|
"path": "tests/redteam_patrol.mjs",
|
|
8569
8579
|
"mode": "0000644",
|
|
8570
|
-
"sha256": "
|
|
8580
|
+
"sha256": "2dbaca4570cdac06c9ebdf2447b9955edeb1afdd763f24d98faec2103001a9e9"
|
|
8571
8581
|
},
|
|
8572
8582
|
{
|
|
8573
8583
|
"path": "tests/regrade_eligibility.mjs",
|
|
@@ -8619,6 +8629,11 @@
|
|
|
8619
8629
|
"mode": "0000644",
|
|
8620
8630
|
"sha256": "b56a6945713e738542d612f6d43dd9bd00ce1a370b3d7b3e130f4e59a056ee15"
|
|
8621
8631
|
},
|
|
8632
|
+
{
|
|
8633
|
+
"path": "tests/routine_schedule.mjs",
|
|
8634
|
+
"mode": "0000644",
|
|
8635
|
+
"sha256": "ba7f5690b9a3649a4c4cd2571d316aa837ffc01874d57aaa418dc72683c6a1f1"
|
|
8636
|
+
},
|
|
8622
8637
|
{
|
|
8623
8638
|
"path": "tests/routine_timers.mjs",
|
|
8624
8639
|
"mode": "0000644",
|
|
@@ -8632,7 +8647,7 @@
|
|
|
8632
8647
|
{
|
|
8633
8648
|
"path": "tests/run_routine.mjs",
|
|
8634
8649
|
"mode": "0000644",
|
|
8635
|
-
"sha256": "
|
|
8650
|
+
"sha256": "a33afdf5b7cbe592f77d5162ad8129f449efb0872ed2b484725c6d1bf0ba27c7"
|
|
8636
8651
|
},
|
|
8637
8652
|
{
|
|
8638
8653
|
"path": "tests/run_routine_two_root.mjs",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# 0188 — The weekly patrol runs on CI, and a declared cadence must have something that fires it
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-08-24
|
|
5
|
+
- **Tasks:** [#1003209](https://cloudbongos.com/builders#/task/1003209) (goal 1000079 — pay down the frozen debt and make enforcement durable; criterion C4 `reduction-durable`)
|
|
6
|
+
- **Builds on:** [ADR 0078](<redacted>.md) (deterministic-first routines + the `run-routine.js` dispatcher), [ADR 0180](<redacted>.md) §3 (the patrol's dependency checks), [ADR 0022](0022-secrets-policy.md) (the prevent → gate → patrol layering)
|
|
7
|
+
- **Related:** [ADR 0162](<redacted>.md) (patrol findings are advisory — verdicts never self-execute), [ADR 0093](<redacted>.md) §1 (where `routine-timers.js` landed — the in-process poller seam this deliberately does *not* use)
|
|
8
|
+
|
|
9
|
+
## 1. Context
|
|
10
|
+
|
|
11
|
+
Goal 1000076 shipped a weekly red-team patrol: `scripts/gds/redteam-patrol.js`
|
|
12
|
+
(full-git-history secret scan, dependency-drift review, workflow action-pin
|
|
13
|
+
check), the `.claude/scheduled-tasks/redteam-weekly` routine that describes how
|
|
14
|
+
to run it, a recipe, and a row in `control-manifest.js` asserting the control
|
|
15
|
+
still exists.
|
|
16
|
+
|
|
17
|
+
Nothing ever ran it. The follow-up audit
|
|
18
|
+
([`docs/audits/ai-dev-redteam-followup.md`](../audits/ai-dev-redteam-followup.md) §7)
|
|
19
|
+
put the failure in three parts:
|
|
20
|
+
|
|
21
|
+
1. **No committed scheduler** for `.claude/scheduled-tasks/` at all. ADR 0078
|
|
22
|
+
built the external dispatcher an operator was meant to wire into cron; the
|
|
23
|
+
wiring never happened, which `routine-timers.js`'s own header already admits
|
|
24
|
+
in so many words — *"nothing was ever durably scheduled."*
|
|
25
|
+
2. **No cadence to read.** The SKILL.md declared `mode:` and `steps:` — *how* to
|
|
26
|
+
run — but never *how often*. There was nothing for a scheduler to honour even
|
|
27
|
+
if one had looked.
|
|
28
|
+
3. **A manifest that was green anyway.** `control-manifest.js` proved the patrol
|
|
29
|
+
"wired" by finding the substring `redteam-patrol.js` inside the SKILL.md —
|
|
30
|
+
i.e. the routine mentioning its own scanner. The control read as connected
|
|
31
|
+
for months while it ran zero times.
|
|
32
|
+
|
|
33
|
+
This is the same shape the ADR 0022 audit found in the first place: a control
|
|
34
|
+
that can quietly stop existing (or, here, quietly never start) is not a control.
|
|
35
|
+
It is also the fifth instance in this goal of a more specific pattern — **an
|
|
36
|
+
enforcement surface that names a file goes VACUOUS, not red, when the thing it
|
|
37
|
+
names moves or stops.**
|
|
38
|
+
|
|
39
|
+
## 2. Decision
|
|
40
|
+
|
|
41
|
+
### 2.1 The runner is CI, not the always-on server
|
|
42
|
+
|
|
43
|
+
The obvious symmetry was to add the patrol beside `peer-vote-tally` in
|
|
44
|
+
`modules/autonomy/pollers/routine-timers.js` — the in-process timer built (as an
|
|
45
|
+
ADR 0078 amendment) for exactly the "deterministic routine nobody scheduled"
|
|
46
|
+
problem, whose header even reasons through why an in-process timer beat external
|
|
47
|
+
cron *there*.
|
|
48
|
+
|
|
49
|
+
**That reasoning does not transfer, and applying it here would have produced a
|
|
50
|
+
patrol that reports clean while scanning nothing.** All three checks read things
|
|
51
|
+
only the *repository* has:
|
|
52
|
+
|
|
53
|
+
| check | needs |
|
|
54
|
+
|---|---|
|
|
55
|
+
| history-secrets | the full git history |
|
|
56
|
+
| action-pins | `.github/workflows/` |
|
|
57
|
+
| dependency-review | a `package.json` + `package-lock.json` npm can audit |
|
|
58
|
+
|
|
59
|
+
A deployed instance runs a **vendored** core out of `node_modules/@bongos/core`.
|
|
60
|
+
That directory is not a git repo, carries no workflows, and ships no lockfile.
|
|
61
|
+
The timer would have found no history to scan, no workflows to check, and
|
|
62
|
+
nothing to audit — and every one of those degrades to *"nothing found"*. Worse
|
|
63
|
+
than unscheduled: confidently, weekly, wrong.
|
|
64
|
+
|
|
65
|
+
So the scheduler is `.github/workflows/redteam-patrol.yml` — a weekly GitHub
|
|
66
|
+
Actions lane with `fetch-depth: 0` and a pinned, checksum-verified gitleaks
|
|
67
|
+
binary (the missing dependency the audit listed as its own gap). It invokes
|
|
68
|
+
`run-routine.js redteam-weekly` rather than the patrol script directly, so the
|
|
69
|
+
SKILL.md's `steps:` remain the single source of truth for what a run does.
|
|
70
|
+
|
|
71
|
+
### 2.2 `cadence:` is a real frontmatter field
|
|
72
|
+
|
|
73
|
+
`parseRoutineFrontmatter` now reads `cadence: hourly|daily|weekly|monthly`. It
|
|
74
|
+
is **declarative only** — `run-routine.js` still runs a routine exactly once per
|
|
75
|
+
invocation. The cadence is the contract a committed scheduler must honour.
|
|
76
|
+
|
|
77
|
+
### 2.3 A cadence nothing fires is a red build
|
|
78
|
+
|
|
79
|
+
`scripts/gds/routine-schedule.js` (fitness Check 25) is the part that keeps
|
|
80
|
+
§7 from recurring. Its predicate is **relational, not existential**: it reads
|
|
81
|
+
the cadence each routine declares, reads the cron each scheduler carries,
|
|
82
|
+
classifies that cron into the period it actually realizes, and fails when the
|
|
83
|
+
two disagree in either direction —
|
|
84
|
+
|
|
85
|
+
- a routine declares a cadence and no scheduler fires it → red
|
|
86
|
+
- a scheduler dispatches a routine that no longer exists → red (rename rot)
|
|
87
|
+
- the cron's real period differs from the declared cadence → red
|
|
88
|
+
|
|
89
|
+
The third is the one that matters most, because **no existence or substring
|
|
90
|
+
predicate can ever see it.** Edit the cron from weekly to monthly and the file
|
|
91
|
+
still exists, the workflow still names the routine, and every "is it wired?"
|
|
92
|
+
check in the repo stays green.
|
|
93
|
+
|
|
94
|
+
The manifest row was re-pointed to match: "wired" for a scheduled control now
|
|
95
|
+
means the *scheduler* names the routine, not that the routine names itself.
|
|
96
|
+
|
|
97
|
+
### 2.4 Red means "a finding was lost", not "a finding exists"
|
|
98
|
+
|
|
99
|
+
A CI runner has no GDS session, so `--file` would have hit `cli-lib`'s
|
|
100
|
+
interactive 401 path — printing *"run /builder-reauth"* and calling
|
|
101
|
+
`process.exit(2)` into a cron log, indistinguishable from a crash, before the
|
|
102
|
+
findings were ever shown. `--file` now preflights the session and follows the
|
|
103
|
+
`main-audit.js --require-roster` doctrine: report loudly rather than
|
|
104
|
+
green-and-vacuous.
|
|
105
|
+
|
|
106
|
+
The exit code is a small table (`disposition()`), and the choice inside it is
|
|
107
|
+
deliberate. Findings are **advisory** (ADR 0162) — they are filed for
|
|
108
|
+
adjudication, not acted on — so a run that finds three things and files them is
|
|
109
|
+
a **success**:
|
|
110
|
+
|
|
111
|
+
| state | exit |
|
|
112
|
+
|---|---|
|
|
113
|
+
| ran, nothing to file | 0 |
|
|
114
|
+
| ran, dry run | 0 |
|
|
115
|
+
| ran, findings **filed** | 0 |
|
|
116
|
+
| ran, findings it **cannot** file | **2** |
|
|
117
|
+
|
|
118
|
+
Red is reserved for the single state where evidence evaporates. The alternative
|
|
119
|
+
— failing whenever findings exist — would have gone red on the very first run
|
|
120
|
+
and stayed red: the staging credential in commit `092e473` is still in history,
|
|
121
|
+
unrotated, so the history scan has a standing hit. A weekly job that is always
|
|
122
|
+
red is a weekly job nobody reads, which is the failure this whole goal is about.
|
|
123
|
+
|
|
124
|
+
Filing itself is optional and credential-gated: with no `GDS_PATROL_SESSION`
|
|
125
|
+
secret the patrol still runs every week and writes findings to the job summary.
|
|
126
|
+
That default matters because this same goal's C1 is about *removing* long-lived
|
|
127
|
+
tokens — the patrol must not require a new one to be useful.
|
|
128
|
+
|
|
129
|
+
## 3. Consequences
|
|
130
|
+
|
|
131
|
+
- The patrol runs unattended for the first time. The first scheduled runs are
|
|
132
|
+
expected to file a `history-secrets` report until commit `092e473` is scrubbed.
|
|
133
|
+
- Adding a routine to `.claude/scheduled-tasks/` now forces a choice: declare a
|
|
134
|
+
cadence and schedule it, or declare none and accept a warning that it is
|
|
135
|
+
manual-only. Silence is no longer an option.
|
|
136
|
+
- The check is tolerant of the two-repo split: `.claude/scheduled-tasks/` is
|
|
137
|
+
materialized into every instance, `.github/workflows/` is the core repo's and
|
|
138
|
+
does not travel — so in a checkout with no workflows directory, unscheduled
|
|
139
|
+
routines are warnings, not violations.
|
|
140
|
+
- `cronCadence()` refuses step values, lists and ranges rather than guessing.
|
|
141
|
+
An exotic schedule cannot be described by a cadence word and must be reworked
|
|
142
|
+
or left manual — the safe direction, since a clever cron silently counting as
|
|
143
|
+
`weekly` would reopen the drift this exists to catch.
|
|
144
|
+
|
|
145
|
+
## 4. Alternatives rejected
|
|
146
|
+
|
|
147
|
+
- **An in-process timer in `routine-timers.js`.** §2.1 — the server cannot see
|
|
148
|
+
the evidence. Rejected on correctness, not cost.
|
|
149
|
+
- **Teaching `routine-timers.js` to discover `.claude/scheduled-tasks/` and
|
|
150
|
+
honour `cadence:` generically.** The task offered this first and it is the more
|
|
151
|
+
general mechanism, but its only candidate user cannot run there, so it would
|
|
152
|
+
have been a scheduler built for nobody. The `cadence:` field is deliberately
|
|
153
|
+
*scheduler-agnostic*, so an instance that later wants an in-process lane for a
|
|
154
|
+
server-side routine can read the same declaration.
|
|
155
|
+
- **Failing the job whenever findings exist.** Permanent red, then ignored. §2.4.
|
|
156
|
+
- **Requiring a GDS session secret.** Would have made the whole lane inert until
|
|
157
|
+
a credential was provisioned, and adds a long-lived token to a goal whose C1 is
|
|
158
|
+
about eliminating one.
|
|
159
|
+
- **A deeper `control-manifest.js` predicate instead of a new check.** The
|
|
160
|
+
manifest's shallowness is deliberate and documented (existence + one wiring
|
|
161
|
+
substring); the cadence↔cron agreement is relational and does not fit that
|
|
162
|
+
shape. Re-pointing the row *and* adding the check keeps each honest.
|
package/docs/adr/README.md
CHANGED
|
@@ -261,5 +261,6 @@ This keeps the decision history honest and traceable.
|
|
|
261
261
|
| 0185 | [**The spark-handoff credit split: 70% developer, 30% spark author** ([task 1002923](https://cloudbongos.com/builders#/task/1002923), BV1.R147, goal 1000062 — completing the chain [task 1002920](https://cloudbongos.com/builders#/task/1002920) mark → [task 1002921](https://cloudbongos.com/builders#/task/1002921) queue → [task 1002922](https://cloudbongos.com/builders#/task/1002922) pickup). [ADR 0172](<redacted>.md) built the ideator streams and paid `captured_by`, the only person an idea then had. The spark handoff created a second: R146 develops a spark IN PLACE, preserving the author while recording a `developed_by`. **R146 therefore shipped paying NOTHING on purpose** — not an oversight but a refusal to guess, because `awardIdeaFullPassCredit`'s idempotency predicate is `(builder_id, reason)`, so paying the DEVELOPER would not have blocked a later payment to the AUTHOR and one idea would have paid twice; the cost of that caution was that a developed Full Idea received stream 2 **never** unless its author resubmitted. Owner decision (2026-08-22): **70/30 favouring the developer**, on BOTH existing streams, one ratio so there is one rule to explain. The reasoning is behavioural, not sentimental — developing someone else's spark is the scarce act the queue depends on, and at an even split it pays HALF what authoring your own Full Idea pays for the same writing work, so the rational move is always to write your own and the R145 queue stays a list nobody reads. The author keeps 30% plus their share of stream 1 on every task shipped under the idea, forever. `IDEA_DEVELOPER_SHARE = 0.70` is a **policy constant in `credits.js`** beside the pass bar and quick factor (ADR 0172's own consequence, the ADR 0023 precedent): a share of someone's earnings deserves a review and a commit, not a runtime edit. **The structural half is what retires R146's caution:** the economy module now derives the payees itself (`ideaCreditParties` reads `captured_by` + `developed_by`), so the caller can no longer choose a recipient — the develop route hands over an idea and a score, never a payee, and books the split like any other path (fail-open; the development has already committed). Three arithmetic properties, each a real bug if missed: the total is **conserved** (developer rounded, author gets the REMAINDER — rounding both pays 6 out of a 5-credit payout); a **self-developed** idea collapses to ONE payee, because two rows for one builder would silently UNDER-pay under the per-builder idempotency predicate, not double-pay (the comparison normalises via `String()` since `bigint` arrives as a string, so a type mismatch cannot become a phantom payee); and **zero-value rows are dropped**. Ids otherwise pass through unconverted — `Number()`-ing a `bigint` risks precision. `builder_id` in the idempotency predicate, which entered as an index optimisation (the R143/IC2 grader finding), is now **load-bearing for correctness**: two payees share one `reason`, so a reason-only predicate would refuse the second, and a partially-booked split completes on the next call. **Not decided here:** stream 3's split (ships with the Board Room, task 1002999), and whether `origin='develop'` in `ideas_task_lineage` belongs to whoever promotes a DEVELOPED idea into a task — ADR 0172 §5 says the row is "set at promotion and at develop (R146)", but R146 creates no task, so that origin stays deliberately unwired rather than invented; it changes who stream 1 pays.](<redacted>.md) | economy / ideator lane |
|
|
262
262
|
| 0186 | [**Per-project idea templates: the seam is already built; the economics are the open problem** ([task 1002932](https://cloudbongos.com/builders#/task/1002932), BV1.R156, goal 1000062 — a SPIKE, output is this ADR plus a task list, no implementation). The literal question — how does a founder define their own idea shape without a schema change or forking the scorer — is **already answered by shipped work**: R141 gave every template function an optional `registry` defaulting to the built-in one, so validation and scoring both read the declaration as data; `ideas_grades.template_key` is per-row with a format CHECK whose own comment says a project key "stays addressable"; and `provisioning_instances.settings jsonb` is an existing per-project home. **No schema change, no fork.** Decision A: a **`project-settings` kernel port**, sibling of `builder-settings`, read via `resolveOptional` so a vanilla instance with no provisioning module degrades to the built-in registry; `base-v1` stays the default and the fallback for an unresolvable key. Rejected a new `ideas_templates` table (second home for data that has one) and a config file (per-instance, but one instance hosts many projects). **Decision B is the finding that matters.** Completeness is normalised 0–100 WITHIN its template (mean of per-field ratios), while karma reads it against an absolute bar (`IDEA_FULL_PASS_BAR = 60`, `IDEA_FULL_PASS_CREDIT = 5`) — so template choice becomes a credit-minting lever: `base-v1` needs ~200 characters for 5 credits, a 1-field template with `substanceMin` 10 needs ~10, a ~20× arbitrage handed to exactly the projects this ADR empowers. Shipping A without B would make the feature a way to print credits. The fix separates the two questions one number is answering: **completeness stays relative** (it is the right signal for triage ordering and the hall meter) and **karma stops reading it**, switching to an absolute substance total the scorer already computes per field and currently discards after averaging. An interim guard (≥3 required fields, ≥120 total `substanceMin`) bounds the arbitrage without fixing it. Decision C: an optional per-field `weight` (default 1) turning the unweighted mean into a weighted one — today's behaviour is the all-weights-1 case, so it is a data addition and one arithmetic line. Decision D: the five base fields are **not** mandatory (forcing them would make "define your own" cosmetic), replaced by two constraints — a template must declare ≥1 required field, and a stored template is validated at WRITE time so a malformed one is refused there rather than discovered inside the capture path, which must never throw. **Boundary note:** goal 1000062's wall is `[ideas, hall-ui]`, so the port task (provisioning + kernel) and the karma task (economy) cannot be claimed under it.](<redacted>.md) | ideas / templates |
|
|
263
263
|
| 0187 | [**The OAuth web-handshake cookies live 30 minutes, and move together** ([task 1003234](https://cloudbongos.com/builders#/task/1003234), goal 1000058). The three cookies `/auth/web/start` sets — the CSRF state, the validated return target, the repo-scope opt-in — were 10 minutes each, a hall builder's timescale; a founder's first sign-in can include a GitHub SIGNUP with email verification and came back past ten minutes to "State mismatch" with the return + repo scope gone, and the page's retry door was a bare start. Decision: one `HANDSHAKE_COOKIE_MS = 30 min` for all three (they MUST move together — a state outliving its return cookie lands on the default landing with no scope, a return outliving its state lands on State mismatch), and every OAuth error page plus the access-pending auto-walk-in rebuilds its door from whatever cookies are still valid (`recoveryStartHref`, through the same open-redirect allowlist). What did NOT change: the state is still single-use, still `httpOnly`/`sameSite=lax`/apex-scoped, the code still the bearer — only the window for a legitimate handshake grew. Rejected: lengthening only return + repos (keeps the dead end and breaks the one-lifetime invariant the other way), a server-side state store, sixty minutes. The Discord-link state cookie is a different flow and keeps 10.](<redacted>.md) | auth / security posture |
|
|
264
|
+
| 0188 | [**The weekly patrol runs on CI, and a declared cadence must have something that fires it** ([task 1003209](https://cloudbongos.com/builders#/task/1003209), goal 1000079). Goal 1000076 shipped a weekly red-team patrol, a routine describing how to run it, a recipe, and a manifest row — and nothing ever ran it; the follow-up audit §7 named all three halves (no committed scheduler, no cadence for a scheduler to read, and a manifest green anyway because its wiring predicate was satisfied by the SKILL.md mentioning its own scanner). The symmetric fix — an in-process timer beside `peer-vote-tally` in `routine-timers.js` — is **wrong on correctness, not cost**: all three patrol checks read things only the REPO has (full git history, `.github/workflows/`, an auditable lockfile), and a deployed instance runs a VENDORED core out of `node_modules/@bongos/core` that has none of them, so the timer would have scanned nothing and reported clean — worse than unscheduled. The runner is therefore CI: a weekly Actions lane with `fetch-depth: 0` and a pinned, checksum-verified gitleaks (the missing binary the audit listed separately), invoking `run-routine.js redteam-weekly` so the SKILL.md's steps stay the single source of truth. `cadence:` becomes a real frontmatter field — declarative only, scheduler-agnostic. The load-bearing piece is `routine-schedule.js` (fitness Check 25), whose predicate is **relational, not existential**: it classifies the cron into the period it actually realizes and fails when that disagrees with the declared cadence — the one rot mode no substring or existence check can EVER see, since editing weekly→monthly leaves every "is it wired?" predicate green. The manifest row was re-pointed to match ("wired" now means the SCHEDULER names the routine). Finally the exit code says **a finding was LOST, not that a finding exists**: findings are advisory (ADR 0162), so filing three is a SUCCESS and red is reserved for findings that cannot be recorded — failing on findings would have gone red on run one and stayed there (the unrotated staging credential in `092e473` is a standing history hit), and an always-red weekly job is one nobody reads. Filing is credential-gated but OPTIONAL — with no session secret the patrol still runs and writes its findings to the job summary, which matters because this goal's C1 is about eliminating long-lived tokens, not adding one. Rejected: the in-process timer; teaching `routine-timers.js` generic cadence discovery (a scheduler built for nobody — its only candidate user cannot run there); failing whenever findings exist; requiring the secret; a deeper manifest predicate instead of a new check.](<redacted>.md) | security / enforcement durability |
|
|
264
265
|
|
|
265
266
|
> ⚠️ **Numbering collision (known):** two ADRs share `0008`. The numbers were assigned in parallel sessions before the lock could be enforced. The files are kept under their existing filenames to preserve git history, but the table disambiguates them as `0008-a` / `0008-b`. New ADRs should resume at `0040` to avoid further collisions. (`0024` is also a real on-disk duplicate: the cloneable-repo/local-first-memory ADR and the multi-agent-system-architecture ADR were both numbered `0024` in parallel; both keep their filenames, disambiguated as `0024-a` / `0024-b` like `0008`. Likewise `0025` ended up with two on-disk files — the offsite-backup ADR and the structured-criterion↔task-link ADR — both kept under their existing filenames and disambiguated as `0025-a` / `0025-b`; separately, the BFG ADR had also claimed `0025` on 2026-05-30 and yielded to `0026`. A third happened 2026-06-06: `0036` was claimed in parallel by the builders-subdomain ADR ([#726](https://example.com/builders#/task/726)) and this Discord-channels ADR ([#727](https://example.com/builders#/task/727)); the Discord-channels ADR yielded to `0037`. A fourth landed 2026-06-10: `0044` was claimed in parallel by the palette-replacement ADR and the per-box game-preview ADR ([#897](https://example.com/builders#/task/897)); both keep their filenames, disambiguated as `0044-a` / `0044-b` like `0008`. A fifth was caught pre-land 2026-06-13: `0050` was claimed in parallel by the device-flow-admission ADR ([#1000](https://example.com/builders#/task/1000)) and the sandbox-for-everyone ADR ([#1001](https://example.com/builders#/task/1001)); the latter yielded to `0052` before merging, so there is no `0050` duplicate. A sixth was caught pre-land 2026-06-21: the tool-agnostic-design-layer ADR ([#1375](https://example.com/builders#/task/1375)) first collided at `0079` with the value-gated-autonomous-engagement ADR, then at `0080` with the llm-cache-api-transport ADR (both part of the 0075–0080 LLM-cost-reduction series, landed on `main` in parallel); it yielded to `0081` before merging, so there is no `0079`/`0080` design-layer duplicate. A seventh landed 2026-06-23: `0084` was claimed in parallel by the auto-derived-per-task-rank-floor ADR and the remove-default-spend-cap ADR (task 1499); the rank-floor ADR landed on `main` first, so the spend-cap ADR yielded to `0085`, leaving no `0084` duplicate. An eighth landed 2026-06-27: `0095` was claimed in parallel by the borrowed-memory-and-retrieval-concepts ADR (PR #459, landed first) and the cross-agent-context-management ADR ([#1651](https://example.com/builders#/task/1651), PR #460); both keep their filenames, disambiguated as `0095-a` / `0095-b` like `0008`. These two are companions — `0095-a` adopts the memory/retrieval concepts and explicitly defers the grep-vs-semantic question to `0095-b`.)
|