@bongos/core 1.19.340 → 1.19.342

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 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.340",
6
- "core_contract": "1.19.340",
7
- "source_commit": "729dc8ef4deb583cef9971c4f3a6af6ed260bb33",
5
+ "core_version": "1.19.342",
6
+ "core_contract": "1.19.342",
7
+ "source_commit": "8762dcc095daf67a6ebc7d8f67bee76335f6d718",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-25T01:39:02.466Z",
9
+ "built_at": "2026-08-25T01:51:12.583Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 325,
12
+ "docs_redacted": 326,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1483,
14
+ "functional_verbatim": 1487,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1830,
20
- "tree_sha256": "5236060970e82dd343f21af8e59eeae712bcc806ea439937aeeb72054b081eef",
19
+ "file_count": 1835,
20
+ "tree_sha256": "77392f5c78b2253320505f4775d1f72a1dfe9c6c07fb3120ca3cbb91c9a8727d",
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": "87606f8e07f470f0ff55d7ac733de9d1004d3d15eddf1003a8958566b4147077"
1430
+ "sha256": "4c736c015e635ed0b4e78445f35acbd8b84a14708634e5caf4a957357b1e22b9"
1426
1431
  },
1427
1432
  {
1428
1433
  "path": "docs/api-reference.md",
@@ -1572,7 +1577,7 @@
1572
1577
  {
1573
1578
  "path": "docs/file-map.md",
1574
1579
  "mode": "0000644",
1575
- "sha256": "ffd052f893ea0ef47a65e66a6d260fd43c1ccc653fa277f11f13805099e73d45"
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": "3dbdc33eb1aa68da5e6569e836845f09fd14953d94794e005af67ce517fb6dad"
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": "d536e9a3a768714780abe658d3dd1e933deb5b1780f4ebc0e5b89c734ac5d0b9"
4735
+ "sha256": "c98fa68956d00010ef831bab66ad73939ee7b86b61a95c2512d4c98b2d284823"
4731
4736
  },
4732
4737
  {
4733
4738
  "path": "package.json",
4734
4739
  "mode": "0000644",
4735
- "sha256": "c7f3a1d37b5cf6e9b0da46656d61cd8e9808f2ade90d8402a2f896ccde4f4c01"
4740
+ "sha256": "6cd5475e1b29d9ba180f44e88c824eb38291945b3122ae22ff948e481333e513"
4736
4741
  },
4737
4742
  {
4738
4743
  "path": "public-docs/index.html",
@@ -5262,7 +5267,7 @@
5262
5267
  {
5263
5268
  "path": "scripts/gds/control-manifest.js",
5264
5269
  "mode": "0000644",
5265
- "sha256": "189caa1e5c263beb5d2c251d924abfe6ea84b7f8b26409a93633c8466bd5801c"
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": "0181ba9d5595199777a84b1bc8363d5cda55df68c1eeecb647a18c4fbb6697df"
5415
+ "sha256": "925006ed968209632b20677d05594158ebc27e65229d96a7d69a53baf93f6ed0"
5411
5416
  },
5412
5417
  {
5413
5418
  "path": "scripts/gds/gate-review.js",
@@ -5754,6 +5759,11 @@
5754
5759
  "mode": "0000644",
5755
5760
  "sha256": "23c61159820e8b796bb3c84b399c4d8d9f2b1fc5665be5672ec72a354bcedc2e"
5756
5761
  },
5762
+ {
5763
+ "path": "scripts/gds/push-path-brief.js",
5764
+ "mode": "0000644",
5765
+ "sha256": "cd173e293b1053a4070d2f1c3d51ad28d19d978cd3489efe312dc45e38739553"
5766
+ },
5757
5767
  {
5758
5768
  "path": "scripts/gds/read-session-export.js",
5759
5769
  "mode": "0000644",
@@ -5772,7 +5782,7 @@
5772
5782
  {
5773
5783
  "path": "scripts/gds/redteam-patrol.js",
5774
5784
  "mode": "0000644",
5775
- "sha256": "4e4ce11a67de653550851d9e0b9931253c8b100dd4285f2a39c74ba2f932b23c"
5785
+ "sha256": "7460ee330a6bc53176a6ab83cb1019d50fe7d872d8a5bf31486e87edc86059af"
5776
5786
  },
5777
5787
  {
5778
5788
  "path": "scripts/gds/regen-instance-docs.js",
@@ -5799,10 +5809,15 @@
5799
5809
  "mode": "0000644",
5800
5810
  "sha256": "084fa5c4da1e755fa0edde5ec770e5151a9f66a247bca222bc7a2366ccf61748"
5801
5811
  },
5812
+ {
5813
+ "path": "scripts/gds/routine-schedule.js",
5814
+ "mode": "0000644",
5815
+ "sha256": "0d75c6b9be5a67738c91e55cda9084e10d7c64aa49c80ce9c0fa4219afcbd14c"
5816
+ },
5802
5817
  {
5803
5818
  "path": "scripts/gds/run-routine.js",
5804
5819
  "mode": "0000644",
5805
- "sha256": "275098d7069661885fcfc6470d690e4fd18134c475e3d03cb4361a7183f9de08"
5820
+ "sha256": "8b7765f52f4d6e6559a47bbc911bf5edc4acea81a3765b8811be9fd07be8dfb7"
5806
5821
  },
5807
5822
  {
5808
5823
  "path": "scripts/gds/run-unit-tests.js",
@@ -6557,7 +6572,7 @@
6557
6572
  {
6558
6573
  "path": "src/module-api.js",
6559
6574
  "mode": "0000644",
6560
- "sha256": "6a783af54daaa7252a170d835f921a6de968f4f6b7343a459696ddc70888bd58"
6575
+ "sha256": "f26225c679194161265e5994e72566604610a0e41a4a2270579cfa6fbce3d0a2"
6561
6576
  },
6562
6577
  {
6563
6578
  "path": "src/module-loader/catalog.js",
@@ -8524,6 +8539,11 @@
8524
8539
  "mode": "0000644",
8525
8540
  "sha256": "5a9ea0bf7b4d776a6f9036b8bd060fef76335533274ed748e9b9d742da6960d3"
8526
8541
  },
8542
+ {
8543
+ "path": "tests/push_path_brief.mjs",
8544
+ "mode": "0000644",
8545
+ "sha256": "aea6f9ec053bd9477288025b58ed76c166c2d59a24d7ea3481edadba90113887"
8546
+ },
8527
8547
  {
8528
8548
  "path": "tests/rank_gate.mjs",
8529
8549
  "mode": "0000644",
@@ -8567,7 +8587,7 @@
8567
8587
  {
8568
8588
  "path": "tests/redteam_patrol.mjs",
8569
8589
  "mode": "0000644",
8570
- "sha256": "bf32ae5c734fc95873433b7c4e251a6b166c167ccc91c4684d061b97681c3357"
8590
+ "sha256": "2dbaca4570cdac06c9ebdf2447b9955edeb1afdd763f24d98faec2103001a9e9"
8571
8591
  },
8572
8592
  {
8573
8593
  "path": "tests/regrade_eligibility.mjs",
@@ -8619,6 +8639,11 @@
8619
8639
  "mode": "0000644",
8620
8640
  "sha256": "b56a6945713e738542d612f6d43dd9bd00ce1a370b3d7b3e130f4e59a056ee15"
8621
8641
  },
8642
+ {
8643
+ "path": "tests/routine_schedule.mjs",
8644
+ "mode": "0000644",
8645
+ "sha256": "ba7f5690b9a3649a4c4cd2571d316aa837ffc01874d57aaa418dc72683c6a1f1"
8646
+ },
8622
8647
  {
8623
8648
  "path": "tests/routine_timers.mjs",
8624
8649
  "mode": "0000644",
@@ -8632,7 +8657,7 @@
8632
8657
  {
8633
8658
  "path": "tests/run_routine.mjs",
8634
8659
  "mode": "0000644",
8635
- "sha256": "b27259a2ffa07f800d5e74aa6d35b623ecd7de2118f63fe4274e105bbbe81daf"
8660
+ "sha256": "a33afdf5b7cbe592f77d5162ad8129f449efb0872ed2b484725c6d1bf0ba27c7"
8636
8661
  },
8637
8662
  {
8638
8663
  "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.
@@ -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`.)
package/docs/file-map.md CHANGED
@@ -389,7 +389,7 @@ tests/
389
389
  <!-- BEGIN GENERATED FILE-MAP .claude/scheduled-tasks/ (scripts/gds/gen-file-map.js — do not hand-edit; notes live in docs/file-map.notes.json) -->
390
390
  ```
391
391
  .claude/scheduled-tasks/
392
- └── redteam-weekly/SKILL.md ← weekly deterministic red-team patrol (task 1003128): full-history gitleaks scan + dependency drift review (audit delta, cooldown, young pkgs, stale overrides) + action-pin check → files security reports (advisory, ADR 0162)
392
+ └── redteam-weekly/SKILL.md ← weekly deterministic red-team patrol (task 1003128): full-history gitleaks scan + dependency drift review (audit delta, cooldown, young pkgs, stale overrides) + action-pin check → files security reports (advisory, ADR 0162). FIRED BY .github/workflows/redteam-patrol.yml, Mondays 06:17 UTC — `cadence: weekly` here and that cron must agree or fitness Check 25 (scripts/gds/routine-schedule.js) fails the gate (task 1003209, ADR 0188)
393
393
  ```
394
394
  <!-- END GENERATED FILE-MAP .claude/scheduled-tasks/ -->
395
395
 
@@ -16,18 +16,69 @@ node scripts/gds/redteam-patrol.js --canary # self-test detection only
16
16
  node scripts/gds/redteam-patrol.js --canary --file # prove the full pipe (files + dispute-closes)
17
17
  ```
18
18
 
19
- The scheduled form lives at [`.claude/scheduled-tasks/redteam-weekly/`](../../.claude/scheduled-tasks/redteam-weekly/SKILL.md)
20
- (`mode: deterministic`, one step, sink `gds`). Install on a machine per the
21
- file-map convention: symlink the routine dir under `~/.claude/scheduled-tasks/`.
22
- Weekly is the cadence — the checks watch drift measured in days (release-age,
23
- new advisories), not minutes.
19
+ ## What actually fires it
20
+
21
+ **[`.github/workflows/redteam-patrol.yml`](../../.github/workflows/redteam-patrol.yml)
22
+ — Mondays 06:17 UTC** (task 1003209, ADR 0188). Before that lane the patrol was,
23
+ in the follow-up audit's words, a manual script: tracked, documented, wired into
24
+ the control manifest, and run zero times.
25
+
26
+ The routine itself lives at
27
+ [`.claude/scheduled-tasks/redteam-weekly/`](../../.claude/scheduled-tasks/redteam-weekly/SKILL.md)
28
+ (`mode: deterministic`, `cadence: weekly`, one step, sink `gds`) and the workflow
29
+ invokes it through `run-routine.js`, so the SKILL.md's `steps:` stay the single
30
+ source of truth for what a run does. Weekly is the cadence because the checks
31
+ watch drift measured in days (release-age, new advisories), not minutes.
32
+
33
+ **The cadence and the cron are checked against each other.** `cadence: weekly` in
34
+ the SKILL.md and `cron: '17 6 * * 1'` in the workflow must agree, or
35
+ `scripts/gds/routine-schedule.js` (fitness Check 25) fails the merge gate — as it
36
+ does if the workflow stops firing the routine, or fires one that no longer
37
+ exists. Change one and change the other in the same commit. The odd minute is
38
+ deliberate: GitHub queues scheduled workflows and top-of-hour is its worst
39
+ congestion window.
40
+
41
+ **Why CI and not the always-on server.** All three checks read things only the
42
+ *repo* has — full git history, `.github/workflows/`, an auditable lockfile. A
43
+ deployed instance runs a vendored core out of `node_modules/@bongos/core`, which
44
+ has none of them, so an in-process timer would have scanned nothing and reported
45
+ clean. ADR 0188 §2.1.
46
+
47
+ ### Filing from CI (optional)
48
+
49
+ A CI runner has no GDS session, so by default the scheduled run **reports but
50
+ cannot file**: findings go to the Actions job summary and the job exits 2. To let
51
+ it file, set the repo secret **`GDS_PATROL_SESSION`** to a GDS CLI session JSON
52
+ (`{"token":…,"api_base":…}`) for a builder holding `security.report.file`; the
53
+ workflow writes it to the session path `instance-config` resolves and the patrol
54
+ files normally. This is opt-in on purpose — the patrol must not *require* a
55
+ long-lived token to be useful.
56
+
57
+ **Red means a finding was LOST, not that a finding exists.** Findings are
58
+ advisory (ADR 0162), so a run that files three of them is a success:
59
+
60
+ | the run… | exit |
61
+ |---|---|
62
+ | found nothing | 0 |
63
+ | was a dry run | 0 |
64
+ | filed its findings | 0 |
65
+ | has findings it cannot file | **2** |
66
+
67
+ Expect a standing `history-secrets` hit until the staging credential in commit
68
+ `092e473` is scrubbed from history. That is the patrol working, not the lane
69
+ being broken.
24
70
 
25
71
  ## Prerequisites
26
72
 
27
73
  - **gitleaks** on PATH (`brew install gitleaks`) or `GITLEAKS_BIN=<path>`. The
28
74
  history scan degrades LOUDLY (a printed warning, no silent skip) when absent;
29
- the dependency + pin checks still run.
75
+ the dependency + pin checks still run. The scheduled lane installs it itself —
76
+ version-pinned and checksum-verified, the same binary and digest the unit gate
77
+ uses, rather than a mutable action tag (the tj-actions lesson).
30
78
  - A GDS session (the normal builder token) for `--file`; dry runs need nothing.
79
+ `--file` preflights it, so a sessionless run prints its findings and says it
80
+ could not file them — it never drops into the CLI's interactive re-auth wall,
81
+ which in a cron log is indistinguishable from a crash.
31
82
  - Network access to the npm registry for the dependency checks — registry
32
83
  failures print notes and skip that item (advisory layer: fail-open, loud).
33
84
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.340",
3
+ "version": "1.19.342",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.340",
9
+ "version": "1.19.342",
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.340",
3
+ "version": "1.19.342",
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",
@@ -69,10 +69,17 @@ const CONTROLS = [
69
69
  wiredIn: { file: '.github/workflows/unit.yml', needle: '--knip' },
70
70
  },
71
71
  {
72
- label: 'patrol: weekly red-team routine wired to its scanner',
72
+ // Task 1003209 re-pointed this row. It used to prove the routine mentioned
73
+ // its own scanner — a wiring substring satisfied by the SKILL.md talking
74
+ // about itself, which is why the manifest read GREEN for months while the
75
+ // patrol never ran once (follow-up audit §7). "Wired" for a scheduled
76
+ // control has to mean SOMETHING FIRES IT, so the needle now lives in the
77
+ // scheduler. The period itself is checked relationally, where a substring
78
+ // can't reach: scripts/gds/routine-schedule.js (fitness Check 25).
79
+ label: 'patrol: weekly red-team routine is actually scheduled',
73
80
  file: '.claude/scheduled-tasks/redteam-weekly/SKILL.md',
74
- wiredIn: { file: '.claude/scheduled-tasks/redteam-weekly/SKILL.md', needle: 'redteam-patrol.js' },
75
- alsoExists: ['scripts/gds/redteam-patrol.js'],
81
+ wiredIn: { file: '.github/workflows/redteam-patrol.yml', needle: 'run-routine.js redteam-weekly' },
82
+ alsoExists: ['scripts/gds/redteam-patrol.js', 'scripts/gds/routine-schedule.js', 'tests/routine_schedule.mjs'],
76
83
  },
77
84
 
78
85
  // ── goal 1000077's four guards (task 1003169 / G05) ──────────────────────
@@ -1305,6 +1305,7 @@ const CHECKS = [
1305
1305
  // and the check gets its own line back.
1306
1306
  require('./version-literal-guard.js').checkNoVersionLiterals,
1307
1307
  require('./control-manifest.js').checkControlsExist, // Check 21 — task 1003131: goal-1000076 controls assert their own existence (the ADR 0022 assurance-of-assurance)
1308
+ require('./routine-schedule.js').checkRoutineCadencesScheduled, // Check 25 — task 1003209: a routine that declares a cadence must have a scheduler firing it AT that period (ADR 0188)
1308
1309
  ];
1309
1310
 
1310
1311
  function runAll() {
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ //
4
+ // scripts/gds/push-path-brief.js — say out loud, at session start, that this machine
5
+ // cannot push (task 1003259 / idea 1000838).
6
+ //
7
+ // THE GAP. On 2026-08-24 a dev-box agent hit auth failures on every raw git probe,
8
+ // concluded "no push credential exists", and asked the owner to mint a fine-grained
9
+ // PAT and store it in ~/.git-credentials on the box — the exact anti-pattern the
10
+ // server-mediated publish path (ADR 0055) exists to avoid. The agent was not being
11
+ // careless: from inside a box every direct GitHub probe fails BY DESIGN (the clone
12
+ // token is one-shot and scrubbed from .git/config after each fetch — ADR 0031 §6),
13
+ // so "missing credential" is the only story the local evidence supports, and the true
14
+ // state at the time (branch already pushed, PR open, CI red) was invisible to it.
15
+ // Nothing the box session loads says the model out loud. This says it out loud.
16
+ //
17
+ // SO: the third SessionStart truth-teller, beside deploy-divergence.js and
18
+ // strand-watch.js. On the server-publish path (pushVia() 'server' — no authenticated
19
+ // gh, i.e. a dev box or any credential-less checkout) it prints ONE line stating the
20
+ // credential model and where the truth about published work lives
21
+ // (GET /api/gds/tasks/:id/publish-status). On a laptop that pushes locally
22
+ // (pushVia() 'local') it prints NOTHING — a line that fires on every healthy laptop
23
+ // becomes wallpaper and the real one gets scrolled past (the strand-watch lesson).
24
+ //
25
+ // STATIC TEXT ONLY. Nothing builder-authored is interpolated, so this adds no
26
+ // prompt-injection surface (the same trust-boundary rule as strand-watch's
27
+ // no-titles) — the test pins the line to the exported constant.
28
+ //
29
+ // Usage:
30
+ // node scripts/gds/push-path-brief.js # human-readable: which path + why
31
+ // node scripts/gds/push-path-brief.js --line # ONE line or nothing; always exit 0
32
+ //
33
+ // Detection override (tests / forcing): OTB_PUSH_VIA_SERVER=1 (server) / =0 (local).
34
+
35
+ const { hasFlag } = require('./cli-lib');
36
+
37
+ // ---- pure core ---------------------------------------------------------------
38
+
39
+ const BRIEF = 'This machine holds NO GitHub push credential — BY DESIGN (ADR 0031 §6 / ADR 0055): '
40
+ + 'git push / ls-remote / fetch against origin fail with auth errors here, and that is not a fault to fix. '
41
+ + 'Landing is server-mediated — /builder-ship uploads a git bundle and the SERVER pushes, opens the PR, and lands it. '
42
+ + 'NEVER ask the owner for a PAT or credential; that request is always wrong on this machine. '
43
+ + 'To see whether work reached GitHub, use GET /api/gds/tasks/:id/publish-status — never raw git probes.';
44
+
45
+ // briefLine(via) — the unit-testable core: the SessionStart line for this publish
46
+ // path, or null. Null IS the silence (the laptop case, and the undetermined case —
47
+ // a detection failure must not manufacture a warning).
48
+ function briefLine(via) {
49
+ return via === 'server' ? BRIEF : null;
50
+ }
51
+
52
+ // ---- detection ---------------------------------------------------------------
53
+
54
+ // Lazy require so a broken or partial checkout degrades to silence, not a stack
55
+ // trace injected into the session. pushVia() honors OTB_PUSH_VIA_SERVER, else
56
+ // probes for an authenticated gh (absent on every box → 'server' instantly).
57
+ function detectVia() {
58
+ return require('./ship-deploy-target.js').pushVia();
59
+ }
60
+
61
+ // ---- rendering ---------------------------------------------------------------
62
+
63
+ function report(via) {
64
+ console.log('');
65
+ console.log(' Publish path');
66
+ console.log(' ──────────────────────────────────────────────────────────');
67
+ if (via === 'server') {
68
+ console.log(' server-mediated (no authenticated gh — a dev box or credential-less checkout).');
69
+ console.log(` ${BRIEF}`);
70
+ } else if (via === 'local') {
71
+ console.log(' local — this machine pushes with its own git + gh credentials.');
72
+ } else {
73
+ console.log(' undetermined — could not probe the publish path on this machine.');
74
+ }
75
+ console.log('');
76
+ }
77
+
78
+ function main() {
79
+ let via = null;
80
+ try { via = detectVia(); } catch (_) { via = null; }
81
+ if (hasFlag('--line')) {
82
+ // FAIL-OPEN, like the other SessionStart lines: silence on 'local' and on any
83
+ // detection failure; never a nonzero exit on the path every session rides in on.
84
+ const l = briefLine(via);
85
+ if (l) console.log(l);
86
+ process.exitCode = 0;
87
+ return;
88
+ }
89
+ report(via);
90
+ }
91
+
92
+ module.exports = { briefLine, BRIEF };
93
+
94
+ if (require.main === module) main();