@bongos/core 1.21.176 → 1.21.177
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 +61 -361
- package/.claude/skills/planning-session/SKILL.md +2 -2
- package/config/scheduled-routines.json +3 -8
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +1 -1
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +1 -1
- package/docs/canonical-permissions.md +1 -1
- package/docs/file-map.md +3 -8
- package/docs/handoff-template.md +1 -1
- package/docs/module-api-changelog.md +2 -0
- package/docs/onboarding/diagrams/README.md +3 -7
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/recipes/ops-gotchas.md +3 -1
- package/modules/autonomy/db.js +9 -0
- package/modules/autonomy/module.json +2 -2
- package/modules/autonomy/pollers/daily-checks.js +262 -0
- package/modules/autonomy/pollers/routine-timers.js +7 -5
- package/modules/autonomy/rank-roles.js +64 -0
- package/modules/autonomy/server-schedule.js +37 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +14 -0
- package/scripts/bongos/api-alias-caller-scan.js +2 -2
- package/scripts/bongos/audit-authorship.sh +0 -5
- package/scripts/bongos/audit-rank-roles.js +15 -56
- package/scripts/bongos/codemod-rename-scripts-gds.js +0 -1
- package/scripts/bongos/diagram-facts.js +1 -1
- package/scripts/bongos/do-api.js +1 -1
- package/scripts/bongos/fitness-checks-identity.js +4 -15
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/pulse.js +5 -3
- package/scripts/bongos/routine-schedule.js +38 -3
- package/scripts/bongos/ship-regen.js +2 -3
- package/scripts/security/dep-audit.js +50 -124
- package/src/module-api.js +1 -1
- package/tests/autonomy_daily_checks.mjs +201 -0
- package/tests/claude_materialize.mjs +12 -12
- package/tests/effective_visibility_predicate.mjs +0 -18
- package/tests/routine_capture_kind.mjs +18 -7
- package/tests/routine_schedule.mjs +42 -0
- package/scripts/bongos/artifact-staleness.js +0 -267
- package/scripts/bongos/classify-discipline.js +0 -153
- package/scripts/bongos/codemod-rename-src-gds.js +0 -145
- package/scripts/bongos/coreB-carve-proof.sh +0 -725
- package/scripts/bongos/goal-names.js +0 -102
- package/scripts/bongos/rename-goals.js +0 -109
- package/scripts/bongos/seed-adopt-tasks.js +0 -75
- package/scripts/bongos/seed-api-contract-goal-tasks.js +0 -141
- package/scripts/bongos/seed-board-room-tasks.js +0 -196
- package/scripts/bongos/seed-bongos-app-tasks.js +0 -294
- package/scripts/bongos/seed-bongos-coreB-tranche1-tasks.js +0 -81
- package/scripts/bongos/seed-bongos-coreB-tranche2-tasks.js +0 -100
- package/scripts/bongos/seed-bongos-goal-hierarchy-tasks.js +0 -307
- package/scripts/bongos/seed-bongos-module-system-tasks.js +0 -346
- package/scripts/bongos/seed-bongos-tasks.js +0 -214
- package/scripts/bongos/seed-bongos-v1-seo-tasks.js +0 -104
- package/scripts/bongos/seed-bv1-community-tasks.js +0 -131
- package/scripts/bongos/seed-bv1-privacy-invites-tasks.js +0 -133
- package/scripts/bongos/seed-bv1-profile-page-tasks.js +0 -161
- package/scripts/bongos/seed-bv1-r01-delta-tasks.js +0 -154
- package/scripts/bongos/seed-bv2-tweak-mode-tasks.js +0 -277
- package/scripts/bongos/seed-gds-v4-tasks.js +0 -624
- package/scripts/bongos/seed-governance-tasks.js +0 -340
- package/scripts/bongos/seed-medusa-tasks.js +0 -440
- package/scripts/bongos/seed-module-upstreaming.js +0 -219
- package/scripts/bongos/seed-productization-tasks.js +0 -235
- package/scripts/bongos/seed-project-admin-console-tasks.js +0 -88
- package/scripts/bongos/seed-provisioning-tasks.js +0 -83
- package/scripts/bongos/seed-strict-versioning-tasks.js +0 -275
- package/scripts/bongos/seed-v2-world-tasks.js +0 -283
- package/scripts/gds/artifact-staleness.js +0 -7
- package/scripts/gds/classify-discipline.js +0 -7
- package/scripts/gds/codemod-rename-src-gds.js +0 -7
- package/scripts/gds/coreB-carve-proof.sh +0 -8
- package/scripts/gds/goal-names.js +0 -7
- package/scripts/gds/rename-goals.js +0 -7
- package/scripts/gds/seed-adopt-tasks.js +0 -7
- package/scripts/gds/seed-api-contract-goal-tasks.js +0 -7
- package/scripts/gds/seed-board-room-tasks.js +0 -6
- package/scripts/gds/seed-bongos-app-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +0 -7
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +0 -7
- package/scripts/gds/seed-bongos-module-system-tasks.js +0 -7
- package/scripts/gds/seed-bongos-tasks.js +0 -7
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +0 -6
- package/scripts/gds/seed-bv1-community-tasks.js +0 -6
- package/scripts/gds/seed-bv1-privacy-invites-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profile-page-tasks.js +0 -6
- package/scripts/gds/seed-bv1-r01-delta-tasks.js +0 -6
- package/scripts/gds/seed-bv2-tweak-mode-tasks.js +0 -6
- package/scripts/gds/seed-gds-v4-tasks.js +0 -7
- package/scripts/gds/seed-governance-tasks.js +0 -7
- package/scripts/gds/seed-medusa-tasks.js +0 -7
- package/scripts/gds/seed-module-upstreaming.js +0 -7
- package/scripts/gds/seed-productization-tasks.js +0 -7
- package/scripts/gds/seed-project-admin-console-tasks.js +0 -6
- package/scripts/gds/seed-provisioning-tasks.js +0 -7
- package/scripts/gds/seed-strict-versioning-tasks.js +0 -7
- package/scripts/gds/seed-v2-world-tasks.js +0 -7
- package/scripts/migrate_cost_ledger.sh +0 -178
- package/tests/artifact_staleness.mjs +0 -207
- package/tests/goal_names.mjs +0 -120
- package/tests/newcomer_generator.mjs +0 -48
|
@@ -303,7 +303,7 @@ Once the spec is finalized (Phase 4), **read `docs/specs/<ver>-<goal-slug>.md` a
|
|
|
303
303
|
- **For a brand-new version scaffolded from scratch (DR / a fresh instance):** put the `INSERT INTO done_when_criteria` (with `goal_id`, `ON CONFLICT ... DO UPDATE`) in **`migrations/instance/<NN>_<ver_slug>_planning_seed.sql`** — and recreate the goal idempotently in the same file (the goal is a runtime row a replay won't have). Applies only under `GDS_APPLY_INSTANCE_SEEDS=1`.
|
|
304
304
|
- **Migration** at `migrations/<NN>_<ver_slug>_planning_seed.sql` (CORE — platform-universal, non-scope-truth changes only): `UPDATE versions SET done_when = ...` and `UPDATE tasks` for existing-task priority + V##.R## title prefix (guarded by `WHERE title NOT LIKE 'V<ver>.R%'`). **Do NOT put versions/goals/`done_when_criteria` INSERTs here** — that's scope-truth and fails the `selfhost-boot` gate (ADR 0105).
|
|
305
305
|
|
|
306
|
-
3. **Write a seed script** at `scripts/bongos/seed-<ver_slug>-tasks.js` modeled on `scripts/bongos
|
|
306
|
+
3. **Write a seed script** at `scripts/bongos/seed-<ver_slug>-tasks.js` modeled on `scripts/bongos/<redacted>.js` (or `<redacted>.js`) containing:
|
|
307
307
|
- The TASKS array — one entry per net-new task with full description, kind, discipline, priority, est_minutes, manual_degree, credits_reward, **optional** `touches` (see below), optional `promoted_idea_id`, `depends_on_refs: ['R##', ...]` (the dep edges captured during brainstorm), AND **`criterion_ids: ['Cn', ...]`** — the done-when criterion(s) this task gates. The brainstorm in Phase 3 is already grouped by criterion, so this is just transcribing that grouping. **Use the criterion_id SLUG, not a positional `"Cn"` token.** `Cn` ranks over the whole VERSION's criteria, so on a version holding more than one goal it resolves to some other goal's criterion — which is how all 15 tasks of goal 1000071 were mislinked while the seed printed success (learning 1000188). Since task 1003108 the server **refuses** a positional ref on a multi-goal version (`400 ambiguous_criterion_ref`) rather than guessing, so a slug is the only ref that works there. The server resolves it against the task's version at create (ADR 0025 / task 438). **This replaces the old `**Criterion:** Cn` prose** — don't write that line anymore; pass `criterion_ids` instead so the task is linked in `task_criteria` at creation and `/status` counts it with no backfill.
|
|
308
308
|
- **`touches` is now OPTIONAL (task 882 / [ADR 0049](../../../docs/adr/0049-split-parallel-safety-contract.md)).** Omit it (or leave it blank) unless you already know a tight file set and want the advisory overlap signal during the brainstorm. At first `/builder-ship` the server backfills `touches[]` from the real committed diff — the declared set becomes what git actually reported. Don't burn planning effort predicting file lists; a rough or absent prediction is fine and no longer ship-blocking.
|
|
309
309
|
- **`requires_rank` AUTO-DERIVES — you don't hand-set it per task (task [#1498](https://example.com/builders#/task/1498) / [ADR 0084](../../../docs/adr/0084-auto-derived-per-task-rank-floor.md)).** On create the server floors a task at `metic` when it is `security_sensitive` or its `touches` reach the permission / authz / ship-grade / deploy / migration core (the same protected paths a sub-Metic builder can't push to); everything else stays the open `xenos` default. To deliberately gate a task **higher** than the auto-floor (e.g. owner-only work), pass `requires_rank: 'metic' | 'archon'` on the task — it can only RAISE the floor, never drop below it.
|
|
@@ -395,7 +395,7 @@ node scripts/bongos/api.js GET /api/bongos/inbox
|
|
|
395
395
|
node scripts/bongos/api.js GET /api/bongos/versions/<id>/done-when
|
|
396
396
|
```
|
|
397
397
|
|
|
398
|
-
Phase 5 writes use the same `POST /tasks`, `PATCH /inbox/:id` shape as `idea-triage` and `
|
|
398
|
+
Phase 5 writes use the same `POST /tasks`, `PATCH /inbox/:id` shape as `idea-triage` and `scripts/bongos/<redacted>.js`. Mirror those patterns rather than inventing new ones.
|
|
399
399
|
|
|
400
400
|
## Tone for the user
|
|
401
401
|
|
|
@@ -3,25 +3,20 @@
|
|
|
3
3
|
"defaults": { "scope": "instance-wide", "enabled": true, "requiresAutonomy": false },
|
|
4
4
|
"routines": {
|
|
5
5
|
"artifact-audit": { "scope": "instance-wide", "enabled": true, "cadence": "weekly", "requiresAutonomy": false, "note": "deterministic; task 1003200. Reads main's own history + tree, so it runs in CI (a vendored core is not a git repo) — .github/workflows/artifact-audit.yml." },
|
|
6
|
-
"artifact-staleness": { "scope": "instance-wide", "enabled": true, "cadence": "daily", "requiresAutonomy": false, "note": "deterministic; task 1001930 / ADR 0110 pt.5. INSTANCE-SIDE ONLY, and the opposite of artifact-audit above: it reads the gitignored deploy-generated files on the serving machine's disk, which a CI checkout legitimately does not have — so it declares no cadence in its SKILL.md and no workflow fires it. This cadence is the hint the operator's own scheduler reads." },
|
|
7
6
|
"bfg": { "scope": "instance-wide", "builderAware": true, "enabled": true, "cadence": "nightly", "requiresAutonomy": false, "note": "Parts A+B read-only + always-on; Part C (cross-builder write) is separately gated by BFG_WRITE_ENABLED + a provisioned principal token." },
|
|
8
7
|
"bfg-session-eval": { "scope": "instance-wide", "builderAware": true, "enabled": true, "cadence": "nightly", "requiresAutonomy": false },
|
|
9
8
|
"core-update-subscription": { "scope": "instance-wide", "enabled": true, "cadence": "15min", "requiresAutonomy": true, "note": "Patch-only auto-upgrade of subscribed consumer instances (ADR 0136 / task 2150). Default OFF + opt-in: the roster (config/update-subscriptions.json) ships empty, so this is a no-op until an operator arms autonomy AND lists an instance. Every upgrade is health-gated with auto-rollback (task 2149). CADENCE (task 1003674): this is the knob that decides how long a published release waits before a subscribed instance takes it — at 'daily' a builder's ship reached the live site up to 24h late, because everything upstream is already fast (CI publishes to npm within ~2 min of merge) and this sweep was the only slow step. 15min is the worst-case lag, not a re-check cost: the sweep exits immediately when the registry has nothing newer. FLOOR ~5min: an upgrading run takes ~2 min, and runs must not overlap. Installing the period is the operator's half — see docs/recipes/upgrading-the-core.md." },
|
|
10
|
-
"dep-audit-nightly": { "scope": "instance-wide", "enabled": true, "cadence": "
|
|
11
|
-
"diagram-drift": { "scope": "instance-wide", "enabled": true, "cadence": "daily", "requiresAutonomy": false },
|
|
9
|
+
"dep-audit-nightly": { "scope": "instance-wide", "enabled": true, "cadence": "daily", "requiresAutonomy": false, "note": "deterministic; task 1004878. Fired by the in-process daily-checks poller (modules/autonomy/pollers/daily-checks.js): npm audit of the deployed instance, high/critical findings filed as ONE idea, re-filed only when the set changes." },
|
|
12
10
|
"idea-triage-nightly": { "scope": "instance-wide", "enabled": false, "cadence": "nightly", "requiresAutonomy": true },
|
|
13
11
|
"main-audit": { "scope": "instance-wide", "enabled": true, "cadence": "on-merge", "requiresAutonomy": false, "note": "Trust-boundary backstop — audits commits landed on main." },
|
|
14
|
-
"newcomer-floor": { "scope": "instance-wide", "enabled": true, "cadence": "daily", "requiresAutonomy": false },
|
|
15
|
-
"newcomer-generator": { "scope": "instance-wide", "enabled": true, "cadence": "daily", "requiresAutonomy": false },
|
|
16
12
|
"overnight-builder": { "scope": "instance-wide", "enabled": false, "cadence": "nightly", "requiresAutonomy": true, "note": "Autonomous claim+ship; OFF until the instance arms autonomy." },
|
|
17
13
|
"overnight-code-review": { "scope": "instance-wide", "enabled": false, "cadence": "nightly", "requiresAutonomy": true },
|
|
18
|
-
"peer-vote-tally": { "scope": "instance-wide", "enabled": true, "cadence": "
|
|
19
|
-
"rank-roles-audit": { "scope": "instance-wide", "enabled": true, "cadence": "
|
|
14
|
+
"peer-vote-tally": { "scope": "instance-wide", "enabled": true, "cadence": "weekly", "requiresAutonomy": false, "note": "deterministic; fired weekly by the in-process routine-timers poller (modules/autonomy/pollers/routine-timers.js)." },
|
|
15
|
+
"rank-roles-audit": { "scope": "instance-wide", "enabled": true, "cadence": "daily", "requiresAutonomy": false, "note": "deterministic; read-only; never fatal at boot (idea 1000622) — task 1002873. Fired by the in-process daily-checks poller (task 1004878)." },
|
|
20
16
|
"redteam-weekly": { "scope": "instance-wide", "enabled": true, "cadence": "weekly", "requiresAutonomy": false, "note": "deterministic; task 1003128" },
|
|
21
17
|
"repo-simplify-sweep": { "scope": "instance-wide", "enabled": true, "cadence": "weekly", "requiresAutonomy": false },
|
|
22
18
|
"security-review-weekly": { "scope": "instance-wide", "enabled": true, "cadence": "weekly", "requiresAutonomy": false },
|
|
23
19
|
"skill-usage-review": { "scope": "instance-wide", "enabled": true, "cadence": "nightly", "requiresAutonomy": false },
|
|
24
|
-
"stale-claim-release": { "scope": "instance-wide", "enabled": true, "cadence": "hourly", "requiresAutonomy": false },
|
|
25
20
|
"stranded-rows": { "scope": "instance-wide", "enabled": true, "requiresAutonomy": false, "note": "no cadence: nothing schedules it yet, so it is run by hand — declaring one with no cron firing it fails fitness Check 25 (task 1003775)" }
|
|
26
21
|
}
|
|
27
22
|
}
|
|
@@ -84,7 +84,7 @@ This ADR's deliverable is the decision above **plus** the carve broken into clai
|
|
|
84
84
|
|
|
85
85
|
**Proof of equivalence at every step** (ADR 0083 / 0062 discipline): after each carve, OTB behaves **byte-for-byte the same**. That is the per-task done-when.
|
|
86
86
|
|
|
87
|
-
**Tranche-1 status: PROVEN** (R74, [#1578](https://example.com/builders#/task/1578)). The five claims above are demonstrated, in one re-runnable command, by
|
|
87
|
+
**Tranche-1 status: PROVEN** (R74, [#1578](https://example.com/builders#/task/1578)). The five claims above are demonstrated, in one re-runnable command, by `scripts/gds/coreB-carve-proof.sh` (deleted once the carve was done, task 1004856; in history at dc7d88169): memory + grading are loader-discovered modules; `db-kernel.js` + the memory query slice left the `db.js` monolith; `KERNEL_FILES` is the bounded 17-file roster with zero fitness hard-failures (incl. the three ENFORCING C7 boundary checks); grading resolves through the `grade` kernel port (`seams.resolveOptional('grade')`) with no core→grader import; and the 27 DB-free tests exercising the carved surfaces pass unchanged (byte-identical). The ongoing guard is CI — `fitness.js` (the kernel-imports-no-domain ratchet flips red at zero leaks) + `run-unit-tests.js` on every PR; the script is the consolidated demonstration, not a new gate. Tranche 2 (the remaining ~8 core domains) stays deferred per owner until greenlit.
|
|
88
88
|
|
|
89
89
|
---
|
|
90
90
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
## Context
|
|
12
12
|
|
|
13
|
-
Tranche 1 ([ADR 0091](0091-bounding-the-kernel-and-db-carve.md)) did two things: it **proved the core-carve pattern** on the two cleanest-slicing domains (memory, grading — demonstrated by
|
|
13
|
+
Tranche 1 ([ADR 0091](0091-bounding-the-kernel-and-db-carve.md)) did two things: it **proved the core-carve pattern** on the two cleanest-slicing domains (memory, grading — demonstrated by `scripts/gds/coreB-carve-proof.sh` (deleted once the carve was done, task 1004856; in history at dc7d88169), R74), and it **shipped the infrastructure** every later carve reuses — the bounded kernel roster + `KERNEL_FILES` fitness ratchet (R70), the doorway with `withTx` (R68), the per-domain `db.js` slice pattern + `db-kernel.js` (R69), and the `grade` kernel **port** (R73). That infrastructure is generic: each remaining core domain is now a *pure carve* copying R72/R73, with **no new scaffolding**.
|
|
14
14
|
|
|
15
15
|
What remains is the bulk of the core: ~8 cohesive domains + the two web UIs + the lifecycle state machine ([02 §6](../design/modular-architecture/02-module-map.md) roster). The import-graph scan ([02 §2](../design/modular-architecture/02-module-map.md)) already told us which slice cleanly (economy, ideas, security alongside the proven memory/grading) and which is the coupled cluster (the lifecycle — `tasks → grader/github-push/ship-broadcast`). This ADR turns that evidence into a sequence.
|
|
16
16
|
|
|
@@ -94,7 +94,7 @@ forming the ordered claim-gate tier **`xenos` (1) < `thetes` (2) < `metic` (3) <
|
|
|
94
94
|
deliberate Archon promotion.
|
|
95
95
|
- **`xenos`** — the **sandboxed entry rank** every new GitHub-authed builder defaults to (migration
|
|
96
96
|
029 flipped the column default here). Read-only on public surfaces; may claim + ship **only** tasks
|
|
97
|
-
an Archon
|
|
97
|
+
an Archon has flagged `newcomer_friendly = true` (enforced in
|
|
98
98
|
`db.claimTask` / `claimTasksBatch`, returns `rank_too_low_for_task`). Cannot triage, resolve
|
|
99
99
|
blockers, or run planning/priority sessions. This is also the tier a deactivated/offboarded builder
|
|
100
100
|
is dropped to by `/builder-exit` (migration `025`); for them `status='inactive'` additionally blocks
|
package/docs/file-map.md
CHANGED
|
@@ -431,26 +431,21 @@ tests/
|
|
|
431
431
|
```
|
|
432
432
|
.claude/scheduled-tasks/
|
|
433
433
|
├── artifact-audit/SKILL.md ← weekly artifact audit (task 1003200): every task at shipped/confirmed/completed that DECLARES a code artifact is checked against main's commit messages AND the content of its tracked files — both, because a fix can land under another branch and be cited only in a comment (task 1003212). Anything unproven is filed as a blocker, deduped on the finding set. Scoped to ships on/after the ship-assurance gate (2026-08-19, ADR 0184), before which a DB-only ship recorded no declaration and silence proves nothing. FIRED BY .github/workflows/artifact-audit.yml, Mondays 06:23 UTC — `cadence: weekly` here and that cron must agree or fitness Check 25 (scripts/bongos/routine-schedule.js) fails the gate (task 1003209, ADR 0188)
|
|
434
|
-
├── artifact-staleness/SKILL.md ← freshness backstop for the gitignored deploy-generated navigation docs (task 1001930, ADR 0110 pt.5): docs/repo-map.md and docs/session-log-index.md are untracked, so no PR gate can see them and a broken deploy regen step leaves a stale or missing map with only a journald line as the alarm. Asks each artifact's own generator (--check-whole) whether it still matches the served commit, reports absent and stale separately, and posts findings to Discord #ship-news itself. INSTANCE-SIDE ONLY — a fresh CI clone legitimately lacks these files, so it declares no cadence and no workflow fires it; config/scheduled-routines.json records the intended daily cadence.
|
|
435
434
|
├── bfg/SKILL.md ← nightly BFG pass (MAS Phase 6, ADR 0026) — doc/memory hygiene + DB counter-vs-ledger drift (Part A), per-builder interaction scoring into transferable-practice suggestions (Part B), and the audited cross-builder memory write (Part C, OFF unless BFG_WRITE_ENABLED) (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
436
435
|
├── bfg-session-eval/SKILL.md ← nightly session-inefficiency evaluator (MAS Phase 6D, ADR 0027) — mines the session corpus for wasteful sessions and files the fixes as idea_inbox rows plus a docs/audits report (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
437
436
|
├── core-update-subscription/SKILL.md ← daily patch-only auto-upgrade of subscribed consumer instances (ADR 0136) — deterministic `subscribe.js` step, health-gated with auto-rollback; opt-in and autonomy-gated, so a no-op until an operator arms it (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
438
|
-
├── dep-audit-nightly/SKILL.md ←
|
|
439
|
-
├── diagram-drift/SKILL.md ← daily deterministic backstop that re-derives the onboarding diagrams from migrations plus the live version API and files an idea when the committed ones have drifted (`check.js --file-idea`) (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
437
|
+
├── dep-audit-nightly/SKILL.md ← daily npm audit of the deployed instance (`scripts/security/dep-audit.js`); high/critical findings become ONE idea_inbox row, re-filed only when the set changes — fired in-process by modules/autonomy/pollers/daily-checks.js (task 1004878)
|
|
440
438
|
├── idea-triage-nightly/SKILL.md ← nightly autonomous idea_inbox walk — promotes high-confidence ideas to tasks, discards obvious noise, defers anything ambiguous; autonomy-gated and OFF by default (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
441
439
|
├── main-audit/SKILL.md ← post-merge trust-boundary backstop (ADR 0043 / SEC 863) — audits commits landed on main for sub-Metic edits to permission-sensitive surfaces (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
442
|
-
├── newcomer-floor/SKILL.md ← daily check that the three evergreen newcomer chores (engineer / artist / ideator) stay stocked; the server restocks them via modules/onboarding/newcomer-restock.js (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
443
|
-
├── newcomer-generator/SKILL.md ← daily deterministic generator (`generate.js`) that tops the newcomer_friendly task queue back up to its floor (criterion C1 — newcomer queue always populated) (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
444
440
|
├── overnight-builder/SKILL.md ← autonomous overnight session-runner — claims the highest-priority safe task, works it end-to-end and ships it; autonomy-gated and OFF by default (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
445
441
|
├── overnight-code-review/SKILL.md ← nightly autonomous code-quality review; each finding lands as its own idea_inbox row so it survives triage; autonomy-gated and OFF by default (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
446
|
-
├── peer-vote-tally/SKILL.md ← deterministic tally (`tally.js`) folding builder-on-builder peer votes at least 7 days old into karma_log rows (criterion C2)
|
|
442
|
+
├── peer-vote-tally/SKILL.md ← deterministic tally (`tally.js`) folding builder-on-builder peer votes at least 7 days old into karma_log rows (criterion C2) — fired weekly in-process by modules/autonomy/pollers/routine-timers.js
|
|
447
443
|
├── publish-watch/SKILL.md ← daily publish-lane check (task 1003916): compares every version the release lane ANNOUNCED in its own release commits against what the registry actually holds, and posts anything announced-but-never-delivered to Discord #ship-news. Exists because the 2026-09-13 outage (8 failed runs, 3 hours) was found by a person reading the npm version list — a failed run is one red row nobody watches, and the next merge erases the evidence. Silent on a healthy day by design; exit 2 (could not read) is distinct from exit 1 (found one).
|
|
448
|
-
├── rank-roles-audit/SKILL.md ←
|
|
444
|
+
├── rank-roles-audit/SKILL.md ← daily deterministic check that every builder holds one of the four live ranks, so none silently resolves to zero permissions under the governance_002 backfill's INNER JOIN; detect-only, files one idea_inbox row and never repairs (task 1002873) — fired in-process by modules/autonomy/pollers/daily-checks.js (task 1004878)
|
|
449
445
|
├── 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 0158 §2). FIRED BY .github/workflows/redteam-patrol.yml, Mondays 06:17 UTC — `cadence: weekly` here and that cron must agree or fitness Check 25 (scripts/bongos/routine-schedule.js) fails the gate (task 1003209, ADR 0188)
|
|
450
446
|
├── repo-simplify-sweep/SKILL.md ← weekly repo-cleanliness janitor — runs repo-metrics, files dead-code findings to idea_inbox, and auto-fixes only the narrow safe class (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
451
447
|
├── security-review-weekly/SKILL.md ← weekly security-state review — stale vulnerability_reports, ageing security-tagged tasks, recent high/critical dependency alerts; one Archon broadcast when anything is stale, silent otherwise (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
452
448
|
├── skill-usage-review/SKILL.md ← nightly skill-usage feedback loop — aggregates 24h of skill invocations into friction findings filed as `[skill-friction]` idea_inbox rows (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
453
|
-
├── stale-claim-release/SKILL.md ← hourly sweep releasing claims whose session_pulses heartbeat has been silent for GDS_STALE_CLAIM_HOURS, returning the task to ready and pinging the builder (restored in task 1003365; unscheduled in core — only redteam-weekly has a firing workflow; config/scheduled-routines.json records the intended cadence)
|
|
454
449
|
└── stranded-rows/SKILL.md ← flags an open task whose work ALREADY LANDED on main (task 1003775): a row still in ready/backlog while a commit claims it with a line ending in `(task N)`. Narrower than "mentions N" on purpose — idea 1000807 measured that a mention-scan returns ~44 open rows that merely cite an id. Measured clean at 878 claims vs 434 open rows, and still catches the historical case (task 1002200, whose claim lives in the MERGE commit body). NO cadence declared: nothing schedules it yet, so it is run by hand — wiring a scheduler needs the control plane, and declaring a cadence with no cron firing it fails fitness Check 25 (task 1003209, ADR 0188). Prints ids + fixed vocabulary only, never builder-authored text.
|
|
455
450
|
```
|
|
456
451
|
<!-- END GENERATED FILE-MAP .claude/scheduled-tasks/ -->
|
package/docs/handoff-template.md
CHANGED
|
@@ -70,7 +70,7 @@ If your change touched anything the onboarding diagrams describe — the **rank
|
|
|
70
70
|
- **All five diagrams are auto-generated** by `scripts/bongos/gen-diagrams.js` — run it (`node scripts/bongos/gen-diagrams.js`) and confirm the result looks right; **do not hand-edit any `.mmd`**. The mechanical three (`01-task-lifecycle`, `02-rank-ladder`, `05-versioning`) regenerate from migrations + the public version API. The conceptual two (`03-example-karma`, `04-architecture`) regenerate from the **authenticated** `/tasks/:id` status of their backing tasks (and 03's seeded-achievement count); if no session is present the generator skips them cleanly. Rendering produces both `.svg` (the pannable viewer asset) and `.png` (fallback).
|
|
71
71
|
- To change a diagram's **layout or wording**, edit the matching template function in `gen-diagrams.js` — not the `.mmd`. `assertions.json` is rebuilt by the generator in lockstep, so you don't hand-maintain it.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
There is no drift backstop (the `diagram-drift` routine was retired in task 1004878) — running `gen-diagrams.js` is part of the change.
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
@@ -3189,5 +3189,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3189
3189
|
landed since 1.21.174 with no explicit bump. run 40234506483. (task 1002620)
|
|
3190
3190
|
1.21.176 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3191
3191
|
landed since 1.21.175 with no explicit bump. run 40235567383. (task 1002620)
|
|
3192
|
+
1.21.177 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3193
|
+
landed since 1.21.176 with no explicit bump. run 40241825861. (task 1002620)
|
|
3192
3194
|
---------------------------------------------------------------------------
|
|
3193
3195
|
```
|
|
@@ -69,16 +69,12 @@ The diagrams are surfaced inside the authenticated **builders' hall** at
|
|
|
69
69
|
|
|
70
70
|
## Staying current (auto-update)
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
One mechanism keeps these honest as the system changes (added under [#356](https://example.com/builders#/task/356)):
|
|
73
73
|
|
|
74
74
|
1. **Render-on-deploy** — all five diagrams auto-regenerate on deploy: `scripts/bongos/gen-diagrams.js` re-renders every `.mmd` to its `.svg` + `.png` and rebuilds `assertions.json` in lockstep, so the committed images never drift from their source. (Do **not** hand-edit the `.mmd`/`.svg`/`.png`/`assertions.json` — edit the template in `gen-diagrams.js` and re-run it.)
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
node .claude/scheduled-tasks/diagram-drift/check.js # report only
|
|
78
|
-
node .claude/scheduled-tasks/diagram-drift/check.js --file-idea
|
|
79
|
-
```
|
|
75
|
+
(A second, the `diagram-drift` scheduled check, was retired in task 1004878: it was never scheduled, could not run on the packaged server, and duplicated the regeneration above.)
|
|
80
76
|
|
|
81
|
-
`assertions.json` is the machine-readable record of what these diagrams claim about the live system (rank enum + wired ranks, task-status set, live versions, and the conceptual `architecture` + `example_karma` piece→task slices). It is **rebuilt by `gen-diagrams.js` in lockstep with the diagrams**, so you no longer hand-maintain it
|
|
77
|
+
`assertions.json` is the machine-readable record of what these diagrams claim about the live system (rank enum + wired ranks, task-status set, live versions, and the conceptual `architecture` + `example_karma` piece→task slices). It is **rebuilt by `gen-diagrams.js` in lockstep with the diagrams**, so you no longer hand-maintain it.
|
|
82
78
|
|
|
83
79
|
## Regenerating the diagrams by hand
|
|
84
80
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Machine-readable facts the onboarding diagrams assert about the LIVE system.
|
|
2
|
+
"_comment": "Machine-readable facts the onboarding diagrams assert about the LIVE system. gen-diagrams.js rebuilds them from the live DB/code in lockstep with the diagrams. When a diagram is intentionally updated, update the matching values here in the same commit. Baselined 2026-05-26 under task #356.",
|
|
3
3
|
"rank_ladder": {
|
|
4
4
|
"diagram": "02-rank-ladder",
|
|
5
5
|
"rank_enum": [
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
"drachmae_karma": {
|
|
84
84
|
"diagram": "03-drachmae-karma",
|
|
85
|
-
"_comment": "Pieces of 03-drachmae-karma that are gated on a backing Bongos task. A piece is LIVE iff its task is 'shipped'. live_tasks must ALL be shipped live; pending_tasks must NOT yet be shipped. Rebuilt automatically by scripts/bongos/gen-diagrams.js from the authenticated /tasks/:id status; the diagram is regenerated in lockstep
|
|
85
|
+
"_comment": "Pieces of 03-drachmae-karma that are gated on a backing Bongos task. A piece is LIVE iff its task is 'shipped'. live_tasks must ALL be shipped live; pending_tasks must NOT yet be shipped. Rebuilt automatically by scripts/bongos/gen-diagrams.js from the authenticated /tasks/:id status; the diagram is regenerated in lockstep. D_SHIP (credit_log trigger) and D_ACH (achievement bonuses) have no task gate and are always LIVE. achievements_seeded mirrors 03's 'N seeded' label, counted from the achievements seed INSERTs in migrations/.",
|
|
86
86
|
"live_tasks": [
|
|
87
87
|
{
|
|
88
88
|
"id": 284,
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
},
|
|
116
116
|
"architecture": {
|
|
117
117
|
"diagram": "04-architecture",
|
|
118
|
-
"_comment": "Pieces of 04-architecture gated on a backing Bongos task. A piece is LIVE iff its task is 'shipped'; live_tasks must ALL be shipped, pending_tasks must NOT yet be. Rebuilt by scripts/bongos/gen-diagrams.js in lockstep with the diagram
|
|
118
|
+
"_comment": "Pieces of 04-architecture gated on a backing Bongos task. A piece is LIVE iff its task is 'shipped'; live_tasks must ALL be shipped, pending_tasks must NOT yet be. Rebuilt by scripts/bongos/gen-diagrams.js in lockstep with the diagram. The machine/server/GitHub topology is structural (not task-gated); these are the LIVE/PLANNED markers. MEMORY layer (ADR 0024): 273 schema / 274 sync-on-ship / 275 pull-on-start / 276 isolation. Ship flow: 1025 (server-mediated publish, for a checkout with no push credential), 1028 (GitHub App push credential). The deploy leg is the standalone AUTO-PULL (ADR 0108/0129), not a task-gated GitHub-Actions CI job, so 859 is intentionally absent.",
|
|
119
119
|
"live_tasks": [
|
|
120
120
|
{
|
|
121
121
|
"id": 273,
|
|
@@ -216,11 +216,13 @@ Inside the namespace the file lists as `---------- 1 root root 0`. The runner un
|
|
|
216
216
|
|
|
217
217
|
## Bongos build pipeline
|
|
218
218
|
|
|
219
|
-
###
|
|
219
|
+
### The deterministic routines run as in-process server timers, not external cron
|
|
220
220
|
|
|
221
221
|
- **What:** `peer-vote-tally` (weekly) runs inside the always-on Node process via the autonomy module's poller `modules/autonomy/pollers/routine-timers.js` (ADR 0078 amendment, task 1382; carved into `modules/autonomy/` at BV1.R83) — no `claude -p`, no external cron, no token. The loader's `startModulePollers` starts it from `src/platform-server.js` at boot (task 1002419 — before that fix the call site had been lost with the retired `server.js` and no module poller started at all); it reaches `tallyPeerVotes` through the doorway. It's a no-op anywhere the DB is unreachable (builder machines), so it only does work on the prod droplet.
|
|
222
222
|
- **Disable / move it out-of-process:** set `ROUTINE_TIMERS_DISABLED=1` in `/etc/example/web-source.env` and restart. Also set it on any **second** always-on instance that shares the `example` DB so the tally can't double-fold (the `FOR UPDATE OF pv` row lock is the hard guard if you forget).
|
|
223
223
|
- **Run it manually now:** `node scripts/bongos/run-routine.js peer-vote-tally` (the dispatcher front door still works). The cadence is boot-anchored ("~90s after boot, then weekly"), not a wall-clock Monday job — frequent deploys re-anchor it, which is harmless (the tally is idempotent).
|
|
224
|
+
- **The daily checks** (task 1004878): `rank-roles-audit` and `dep-audit-nightly` run from the sibling poller `modules/autonomy/pollers/daily-checks.js`. Unlike peer-vote it is **not** boot-anchored — deploys restart the server many times a day — so it ticks hourly and runs a check only when that check's last `autonomy_runs` row is ~a day old. Findings go to the idea inbox, once per distinct set (the row stores a fingerprint; an identical result is recorded `unchanged`). See what ran without SSH: `GET /autonomy/runs?routine=dep-audit-nightly`. Same kill switch (`ROUTINE_TIMERS_DISABLED=1`). Which routines the server fires, and how often, is ONE table — `modules/autonomy/server-schedule.js` — read by both the pollers and the merge-gate check `scripts/bongos/routine-schedule.js`.
|
|
225
|
+
- **`dep-audit-nightly` audits the INSTANCE root** (`resolveInstanceRoot()`, the service's working directory), so it needs `npm` on the service's PATH and a `package-lock.json` there. Without either it records a `no-go` row with the reason — never a clean pass.
|
|
224
226
|
|
|
225
227
|
### A ship was graded FAIL but the work is genuinely good (grader false-negative)
|
|
226
228
|
|
package/modules/autonomy/db.js
CHANGED
|
@@ -62,6 +62,14 @@ async function readAutonomyRuns({ routine = null, limit = 20 } = {}) {
|
|
|
62
62
|
return rows.map((r) => ({ ...r, id: String(r.id) }));
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// readRankRoster — every builder's id, login, rank and status, for the daily
|
|
66
|
+
// rank-roles check (task 1004878). No WHERE on purpose: an INACTIVE builder on a
|
|
67
|
+
// legacy rank is exactly the row a future CHECK-narrowing migration would fail on.
|
|
68
|
+
async function readRankRoster() {
|
|
69
|
+
const { rows } = await pool.query('SELECT id, github_login, rank, status FROM builders ORDER BY id');
|
|
70
|
+
return rows;
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
// ── the Autobongos fence (task 1003908, ADR 0307 §6) ─────────────────────────
|
|
66
74
|
//
|
|
67
75
|
// These back the one control surface the runner host cannot write to. Everything
|
|
@@ -350,6 +358,7 @@ module.exports = {
|
|
|
350
358
|
recordAutonomyRun,
|
|
351
359
|
lastAutonomyHead,
|
|
352
360
|
readAutonomyRuns,
|
|
361
|
+
readRankRoster,
|
|
353
362
|
recordRunnerHeartbeat,
|
|
354
363
|
readRunnerHeartbeats,
|
|
355
364
|
readFence,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"key": "autonomy",
|
|
3
3
|
"title": "Autonomy & MAS (conductor · architect · gate · routine timers)",
|
|
4
|
-
"description": "The Multi-Agent System's autonomy surfaces (ADR 0024): the $0 deterministic autonomy gate (System 3 — a precheck route + the gate CLI, inert until OTB_AUTONOMY_ENABLED=1), the conductor mid-flight dispatch CLI (Phase 4), the Architect bounded first-principles --scope audit CLI (Phase 5), and the in-process routine timers (System 4 / ADR 0078 amendment — the weekly peer-vote-tally poller). A core-domain module carved out of the GDS core in BV1.R83 (ADR 0093 §1); default-on because the autonomy machinery + the durable routine timer ship to every instance (both are inert/no-op until armed or the pool is reachable). Cross-cutting: it OBSERVES lifecycle events. The routine timer reaches the lifecycle `tallyPeerVotes` capability through the published doorway (a tracked lifecycle leak until R86), never a core require; the precheck route + the gate/conductor/architect CLIs travel with this domain (the CLIs stay under scripts/, outside the kernel boundary, so they may still read the ideas/economy/lifecycle signals + the grader directly). Registers no port — core never calls back in.",
|
|
4
|
+
"description": "The Multi-Agent System's autonomy surfaces (ADR 0024): the $0 deterministic autonomy gate (System 3 — a precheck route + the gate CLI, inert until OTB_AUTONOMY_ENABLED=1), the conductor mid-flight dispatch CLI (Phase 4), the Architect bounded first-principles --scope audit CLI (Phase 5), and the in-process routine timers (System 4 / ADR 0078 amendment — the weekly peer-vote-tally poller, and since task 1004878 the daily rank-roles and dependency-audit checks). A core-domain module carved out of the GDS core in BV1.R83 (ADR 0093 §1); default-on because the autonomy machinery + the durable routine timer ship to every instance (both are inert/no-op until armed or the pool is reachable). Cross-cutting: it OBSERVES lifecycle events. The routine timer reaches the lifecycle `tallyPeerVotes` capability through the published doorway (a tracked lifecycle leak until R86), never a core require; the precheck route + the gate/conductor/architect CLIs travel with this domain (the CLIs stay under scripts/, outside the kernel boundary, so they may still read the ideas/economy/lifecycle signals + the grader directly). Registers no port — core never calls back in.",
|
|
5
5
|
"version": "1.0.0",
|
|
6
6
|
"coreVersion": "^1.11.0",
|
|
7
7
|
"category": { "parent": "work" },
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"maintenance": { "status": "core-maintained" },
|
|
10
10
|
"contributes": {
|
|
11
11
|
"routes": ["autonomy"],
|
|
12
|
-
"pollers": ["routine-timers"]
|
|
12
|
+
"pollers": ["routine-timers", "daily-checks"]
|
|
13
13
|
},
|
|
14
14
|
"provides": [],
|
|
15
15
|
"consumes": []
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/autonomy/pollers/daily-checks.js — the deterministic daily checks, fired
|
|
4
|
+
// by the always-on server itself (task 1004878).
|
|
5
|
+
//
|
|
6
|
+
// WHY IN-PROCESS. Every one of these routines declared a cadence and nothing fired
|
|
7
|
+
// it: the repo's only scheduler was .github/workflows/, whose minutes are billable
|
|
8
|
+
// and already oversubscribed, and the operator-side cron the SKILL.md files kept
|
|
9
|
+
// asking for was never written. The routine-timers poller beside this one solved
|
|
10
|
+
// the same problem for peer-vote-tally (ADR 0078 amendment) — an in-process timer
|
|
11
|
+
// costs nothing and cannot be forgotten by an operator. This is that pattern for
|
|
12
|
+
// the daily checks.
|
|
13
|
+
//
|
|
14
|
+
// THE CHECKS (the rows of ../server-schedule.js with poller 'daily-checks'):
|
|
15
|
+
// - rank-roles-audit — every builder on one of the live ranks (task 1002873).
|
|
16
|
+
// Reads the roster straight from this instance's DB, so it
|
|
17
|
+
// needs no CLI session — the reason the script never ran
|
|
18
|
+
// unattended.
|
|
19
|
+
// - dep-audit-nightly — `npm audit` of the DEPLOYED instance (its own root and
|
|
20
|
+
// lockfile, not a repo checkout), high/critical only.
|
|
21
|
+
//
|
|
22
|
+
// DAILY BY THE CLOCK, NOT BY THE BOOT. The server restarts on every deploy, and a
|
|
23
|
+
// boot-anchored timer would re-run (and re-file) on each one. So the timer only
|
|
24
|
+
// ticks hourly and asks the ledger: a check runs when its last `autonomy_runs` row
|
|
25
|
+
// is (nearly) a day old. The same rows are what GET /autonomy/runs shows, so
|
|
26
|
+
// "did the daily check run, and what did it find" is answerable from a laptop.
|
|
27
|
+
//
|
|
28
|
+
// ONE IDEA PER DISTINCT FINDING SET. A finding is filed into the idea inbox once;
|
|
29
|
+
// the run row stores a fingerprint of what was found, and the next day's identical
|
|
30
|
+
// result is recorded as `unchanged` instead of filed again. A changed set (a new
|
|
31
|
+
// vulnerability, a second bad rank) files again. A clean run clears it, so a
|
|
32
|
+
// regression after a fix is a fresh idea.
|
|
33
|
+
//
|
|
34
|
+
// Mirrors routine-timers.js (but logs through api.logger, not console): kill switch ROUTINE_TIMERS_DISABLED, unref'd timers,
|
|
35
|
+
// best-effort (a throw logs and waits for the next tick — never crashes the
|
|
36
|
+
// server), pure w.r.t. injected deps so it unit-tests without a database, and a
|
|
37
|
+
// quiet no-op anywhere the database is unreachable (builder laptops).
|
|
38
|
+
|
|
39
|
+
const { execFile } = require('node:child_process');
|
|
40
|
+
const crypto = require('node:crypto');
|
|
41
|
+
const api = require('../../../src/module-api');
|
|
42
|
+
const autonomyDb = require('../db');
|
|
43
|
+
const rankRoles = require('../rank-roles');
|
|
44
|
+
const depAudit = require('../../../scripts/security/dep-audit');
|
|
45
|
+
const { SERVER_ROUTINES, periodMs } = require('../server-schedule');
|
|
46
|
+
|
|
47
|
+
const log = api.logger('autonomy');
|
|
48
|
+
|
|
49
|
+
const ROUTINES = SERVER_ROUTINES.filter((r) => r.poller === 'daily-checks').map((r) => r.routine);
|
|
50
|
+
|
|
51
|
+
const TICK_MS = Number(process.env.ROUTINE_DAILY_CHECKS_TICK_MS) || 60 * 60_000;
|
|
52
|
+
// Later than peer-vote's 90s so the two first ticks never land together.
|
|
53
|
+
const FIRST_MS = Number(process.env.ROUTINE_DAILY_CHECKS_FIRST_MS) || 3 * 60_000;
|
|
54
|
+
const JITTER_MS = 60_000;
|
|
55
|
+
// A check is due this long BEFORE a full period has passed. Hourly ticks land up to
|
|
56
|
+
// an hour late; without the slack each day's run would slide an hour later than the
|
|
57
|
+
// last. With it, runs stay between 23.5 and 24.5 hours apart.
|
|
58
|
+
const DUE_SLACK_MS = 30 * 60_000;
|
|
59
|
+
// npm audit is a network call to the registry; one stuck call must not hold the
|
|
60
|
+
// next day's check hostage.
|
|
61
|
+
const NPM_AUDIT_TIMEOUT_MS = 5 * 60_000;
|
|
62
|
+
|
|
63
|
+
// ── the checks ─────────────────────────────────────────────────────────────────
|
|
64
|
+
// Each returns { keys, idea } — `keys` identify the findings (empty = clean) and
|
|
65
|
+
// feed the fingerprint; `idea` is what gets filed. A check that cannot run THROWS:
|
|
66
|
+
// "could not check" must never be recorded as clean.
|
|
67
|
+
|
|
68
|
+
async function checkRankRoles(deps) {
|
|
69
|
+
const rows = await deps.db.readRankRoster();
|
|
70
|
+
const offenders = rankRoles.findRanksWithoutRoles(rows, deps.liveRanks || api.LIVE_RANK_LADDER);
|
|
71
|
+
return {
|
|
72
|
+
keys: offenders.map((o) => `${o.id}:${o.rank}`),
|
|
73
|
+
idea: { ...rankRoles.ideaFor(offenders, { filedBy: "the server's daily rank-roles check" }), kind: 'bug' },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The async spawn. execFile reports npm's own exit 1 (findings present) as an error
|
|
78
|
+
// too, so only a missing binary or a timeout counts as "could not run".
|
|
79
|
+
function npmAudit(cwd) {
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
execFile('npm', depAudit.NPM_AUDIT_ARGS, { cwd, maxBuffer: 32 * 1024 * 1024, timeout: NPM_AUDIT_TIMEOUT_MS },
|
|
82
|
+
(err, stdout, stderr) => {
|
|
83
|
+
const failed = err && (err.code === 'ENOENT' || err.killed);
|
|
84
|
+
resolve({ stdout, stderr, error: failed ? (err.code === 'ENOENT' ? 'npm not found' : 'npm audit timed out') : null });
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function checkDepAudit(deps) {
|
|
90
|
+
const cwd = deps.cwd || api.resolveInstanceRoot();
|
|
91
|
+
const out = await (deps.npmAudit || npmAudit)(cwd);
|
|
92
|
+
if (out.error) throw new Error(out.error);
|
|
93
|
+
const res = depAudit.parseNpmAudit(out.stdout, out.stderr);
|
|
94
|
+
if (!res.ok) throw new Error(res.raw_error);
|
|
95
|
+
const f = res.findings;
|
|
96
|
+
return {
|
|
97
|
+
keys: f.map((x) => `${x.package}:${x.id || x.summary}`),
|
|
98
|
+
idea: {
|
|
99
|
+
kind: 'infra',
|
|
100
|
+
title: `[dep-audit] ${f.length} high/critical ${f.length === 1 ? 'vulnerability' : 'vulnerabilities'} in this instance's dependencies`,
|
|
101
|
+
bodyMd: [
|
|
102
|
+
`The server's daily \`npm audit\` of this instance (${cwd}) found ${f.length} high/critical advisory(ies):`,
|
|
103
|
+
'',
|
|
104
|
+
...f.map((x) => `- **${x.package}** (${x.severity}) — ${x.summary}${x.url ? ` — ${x.url}` : ''}. Fix: ${x.fix}.`),
|
|
105
|
+
'',
|
|
106
|
+
'Most of these arrive through the core package, so the fix is usually a core release',
|
|
107
|
+
'that bumps the dependency, then moving this instance onto it.',
|
|
108
|
+
'',
|
|
109
|
+
'Filed by modules/autonomy/pollers/daily-checks.js (task 1004878). The same set is not',
|
|
110
|
+
're-filed tomorrow; a changed set is.',
|
|
111
|
+
].join('\n'),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const CHECKS = {
|
|
117
|
+
'rank-roles-audit': checkRankRoles,
|
|
118
|
+
'dep-audit-nightly': checkDepAudit,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// ── the ledger half ────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
const fingerprint = (keys) =>
|
|
124
|
+
crypto.createHash('sha1').update([...keys].sort().join('\n')).digest('hex').slice(0, 12);
|
|
125
|
+
|
|
126
|
+
// The fingerprint the last DELIVERED finding carried, or null. Only a `go` row
|
|
127
|
+
// counts: an error or an undelivered filing in between must not make tomorrow's
|
|
128
|
+
// identical result look new, and must not make it look delivered either.
|
|
129
|
+
function lastFiledFingerprint(rows) {
|
|
130
|
+
const go = (rows || []).find((r) => r.decision === 'go');
|
|
131
|
+
const m = go && /\bfp=([0-9a-f]+)/.exec(go.reason || '');
|
|
132
|
+
return m ? m[1] : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isDue(rows, routine, now) {
|
|
136
|
+
const last = (rows || [])[0];
|
|
137
|
+
if (!last) return true;
|
|
138
|
+
const at = new Date(last.created_at).getTime();
|
|
139
|
+
if (!Number.isFinite(at)) return true;
|
|
140
|
+
return now - at >= periodMs(routine) - DUE_SLACK_MS;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// One routine, once: run it if it is due, file what it found if that is new, and
|
|
144
|
+
// write the row that says so. NEVER throws.
|
|
145
|
+
async function runCheck(routine, deps = {}) {
|
|
146
|
+
const db = deps.db || autonomyDb;
|
|
147
|
+
const logFn = deps.log || ((m) => log.info(m));
|
|
148
|
+
const errFn = deps.error || ((m) => log.error(m));
|
|
149
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
150
|
+
const record = (decision, reason) =>
|
|
151
|
+
db.recordAutonomyRun({ routine, decision, reason }).catch((e) =>
|
|
152
|
+
errFn(`[daily-checks] ${routine}: could not record the run (${e && e.message})`));
|
|
153
|
+
|
|
154
|
+
let rows;
|
|
155
|
+
try {
|
|
156
|
+
rows = await db.readAutonomyRuns({ routine, limit: 10 });
|
|
157
|
+
} catch (e) {
|
|
158
|
+
return { skipped: 'db_unreachable', error: e && e.message };
|
|
159
|
+
}
|
|
160
|
+
if (!isDue(rows, routine, now)) return { skipped: 'not_due' };
|
|
161
|
+
|
|
162
|
+
let result;
|
|
163
|
+
try {
|
|
164
|
+
result = await CHECKS[routine]({ ...deps, db });
|
|
165
|
+
} catch (e) {
|
|
166
|
+
const msg = (e && e.message) || String(e);
|
|
167
|
+
errFn(`[daily-checks] ${routine}: could not run (${msg})`);
|
|
168
|
+
await record('no-go', `error: ${msg}`.slice(0, 500));
|
|
169
|
+
return { outcome: 'error', error: msg };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!result.keys.length) {
|
|
173
|
+
await record('go', 'clean');
|
|
174
|
+
return { outcome: 'clean' };
|
|
175
|
+
}
|
|
176
|
+
const fp = fingerprint(result.keys);
|
|
177
|
+
const n = result.keys.length;
|
|
178
|
+
if (fp === lastFiledFingerprint(rows)) {
|
|
179
|
+
await record('go', `findings n=${n} fp=${fp} unchanged`);
|
|
180
|
+
return { outcome: 'unchanged', fingerprint: fp };
|
|
181
|
+
}
|
|
182
|
+
const capture = deps.captureIdea || api.resolveOptional('ideas.capture');
|
|
183
|
+
if (typeof capture !== 'function') {
|
|
184
|
+
errFn(`[daily-checks] ${routine}: ${n} finding(s) and no idea inbox to file them in (ideas module off)`);
|
|
185
|
+
await record('no-go', `findings n=${n} fp=${fp} undelivered: no ideas.capture`);
|
|
186
|
+
return { outcome: 'undelivered', fingerprint: fp };
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const idea = await capture({ ...result.idea, filedVia: 'api' });
|
|
190
|
+
logFn(`[daily-checks] ${routine}: ${n} finding(s) filed as idea ${idea && idea.id}`);
|
|
191
|
+
await record('go', `findings n=${n} fp=${fp} idea=${idea && idea.id}`);
|
|
192
|
+
return { outcome: 'filed', fingerprint: fp, ideaId: idea && idea.id };
|
|
193
|
+
} catch (e) {
|
|
194
|
+
const msg = (e && (e.message || e.code)) || String(e);
|
|
195
|
+
errFn(`[daily-checks] ${routine}: ${n} finding(s), filing failed (${msg})`);
|
|
196
|
+
await record('no-go', `findings n=${n} fp=${fp} undelivered: ${msg}`.slice(0, 500));
|
|
197
|
+
return { outcome: 'undelivered', fingerprint: fp, error: msg };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// One tick: every daily routine in turn. Sequential on purpose — npm audit is the
|
|
202
|
+
// heavy one and nothing here is urgent enough to run two at once.
|
|
203
|
+
let running = false;
|
|
204
|
+
let warnedUnreachable = false;
|
|
205
|
+
async function tick(deps = {}) {
|
|
206
|
+
if (running) return [];
|
|
207
|
+
running = true;
|
|
208
|
+
try {
|
|
209
|
+
const out = [];
|
|
210
|
+
for (const routine of ROUTINES) {
|
|
211
|
+
const r = await runCheck(routine, deps);
|
|
212
|
+
if (r.skipped === 'db_unreachable' && !warnedUnreachable) {
|
|
213
|
+
warnedUnreachable = true;
|
|
214
|
+
log.info(`[daily-checks] skipped — database unreachable (${r.error}); a builder machine has nothing to check`);
|
|
215
|
+
}
|
|
216
|
+
out.push({ routine, ...r });
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
} finally {
|
|
220
|
+
running = false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let firstTimer = null;
|
|
225
|
+
let interval = null;
|
|
226
|
+
|
|
227
|
+
function start(deps = {}) {
|
|
228
|
+
if (process.env.ROUTINE_TIMERS_DISABLED === '1') {
|
|
229
|
+
log.info('[daily-checks] disabled (ROUTINE_TIMERS_DISABLED=1)');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (firstTimer || interval) return; // already started
|
|
233
|
+
const fire = () => tick(deps).catch((e) => log.error(`[daily-checks] tick crashed: ${e && e.message}`));
|
|
234
|
+
firstTimer = setTimeout(() => {
|
|
235
|
+
fire();
|
|
236
|
+
interval = setInterval(fire, TICK_MS);
|
|
237
|
+
if (interval.unref) interval.unref();
|
|
238
|
+
}, FIRST_MS + Math.floor(Math.random() * JITTER_MS));
|
|
239
|
+
if (firstTimer.unref) firstTimer.unref();
|
|
240
|
+
log.info(`[daily-checks] started — ${ROUTINES.join(', ')} once a day (checked hourly against the run ledger)`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function stop() {
|
|
244
|
+
if (firstTimer) clearTimeout(firstTimer);
|
|
245
|
+
if (interval) clearInterval(interval);
|
|
246
|
+
firstTimer = interval = null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
start,
|
|
251
|
+
stop,
|
|
252
|
+
tick,
|
|
253
|
+
runCheck,
|
|
254
|
+
isDue,
|
|
255
|
+
fingerprint,
|
|
256
|
+
lastFiledFingerprint,
|
|
257
|
+
checkRankRoles,
|
|
258
|
+
checkDepAudit,
|
|
259
|
+
ROUTINES,
|
|
260
|
+
TICK_MS,
|
|
261
|
+
DUE_SLACK_MS,
|
|
262
|
+
};
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
// so over-/under-firing is harmless and a second instance can't double-fold.
|
|
39
39
|
//
|
|
40
40
|
// (diagram-drift was intentionally NOT made a timer: all five onboarding diagrams
|
|
41
|
-
// already auto-regenerate from live facts on every deploy
|
|
42
|
-
//
|
|
43
|
-
//
|
|
41
|
+
// already auto-regenerate from live facts on every deploy. Task 1004878 retired
|
|
42
|
+
// the routine outright. The daily checks that ARE timed live beside this file, in
|
|
43
|
+
// daily-checks.js.)
|
|
44
44
|
//
|
|
45
45
|
// Mirrors the publish-reconciler contract: env-overridable interval, an UNREF'd
|
|
46
46
|
// timer (never holds the process open across a systemd restart), best-effort (a
|
|
@@ -57,9 +57,11 @@
|
|
|
57
57
|
// core load at module-discovery time, and so tests that pass deps.db bypass it.
|
|
58
58
|
const api = require('../../../src/module-api');
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
// The period comes from ../server-schedule.js, the table the merge-gate cadence check
|
|
61
|
+
// (scripts/bongos/routine-schedule.js) also reads — so the two cannot disagree.
|
|
62
|
+
const { periodMs } = require('../server-schedule');
|
|
61
63
|
|
|
62
|
-
const PEER_VOTE_INTERVAL_MS = Number(process.env.ROUTINE_PEER_VOTE_MS) ||
|
|
64
|
+
const PEER_VOTE_INTERVAL_MS = Number(process.env.ROUTINE_PEER_VOTE_MS) || periodMs('peer-vote-tally');
|
|
63
65
|
// First-fire delay: let the server finish binding + the other in-process pollers
|
|
64
66
|
// settle before the first tally. Jitter spreads first-fire across a minute so a
|
|
65
67
|
// crash-loop / deploy storm doesn't synchronize every restart's first run.
|