@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.
Files changed (103) hide show
  1. package/.bongos-core.json +61 -361
  2. package/.claude/skills/planning-session/SKILL.md +2 -2
  3. package/config/scheduled-routines.json +3 -8
  4. package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +1 -1
  5. package/docs/adr/0093-tranche-2-core-carve-sequence.md +1 -1
  6. package/docs/canonical-permissions.md +1 -1
  7. package/docs/file-map.md +3 -8
  8. package/docs/handoff-template.md +1 -1
  9. package/docs/module-api-changelog.md +2 -0
  10. package/docs/onboarding/diagrams/README.md +3 -7
  11. package/docs/onboarding/diagrams/assertions.json +3 -3
  12. package/docs/recipes/ops-gotchas.md +3 -1
  13. package/modules/autonomy/db.js +9 -0
  14. package/modules/autonomy/module.json +2 -2
  15. package/modules/autonomy/pollers/daily-checks.js +262 -0
  16. package/modules/autonomy/pollers/routine-timers.js +7 -5
  17. package/modules/autonomy/rank-roles.js +64 -0
  18. package/modules/autonomy/server-schedule.js +37 -0
  19. package/package-lock.json +2 -2
  20. package/package.json +1 -1
  21. package/release-notes.json +14 -0
  22. package/scripts/bongos/api-alias-caller-scan.js +2 -2
  23. package/scripts/bongos/audit-authorship.sh +0 -5
  24. package/scripts/bongos/audit-rank-roles.js +15 -56
  25. package/scripts/bongos/codemod-rename-scripts-gds.js +0 -1
  26. package/scripts/bongos/diagram-facts.js +1 -1
  27. package/scripts/bongos/do-api.js +1 -1
  28. package/scripts/bongos/fitness-checks-identity.js +4 -15
  29. package/scripts/bongos/gen-diagrams.js +3 -3
  30. package/scripts/bongos/pulse.js +5 -3
  31. package/scripts/bongos/routine-schedule.js +38 -3
  32. package/scripts/bongos/ship-regen.js +2 -3
  33. package/scripts/security/dep-audit.js +50 -124
  34. package/src/module-api.js +1 -1
  35. package/tests/autonomy_daily_checks.mjs +201 -0
  36. package/tests/claude_materialize.mjs +12 -12
  37. package/tests/effective_visibility_predicate.mjs +0 -18
  38. package/tests/routine_capture_kind.mjs +18 -7
  39. package/tests/routine_schedule.mjs +42 -0
  40. package/scripts/bongos/artifact-staleness.js +0 -267
  41. package/scripts/bongos/classify-discipline.js +0 -153
  42. package/scripts/bongos/codemod-rename-src-gds.js +0 -145
  43. package/scripts/bongos/coreB-carve-proof.sh +0 -725
  44. package/scripts/bongos/goal-names.js +0 -102
  45. package/scripts/bongos/rename-goals.js +0 -109
  46. package/scripts/bongos/seed-adopt-tasks.js +0 -75
  47. package/scripts/bongos/seed-api-contract-goal-tasks.js +0 -141
  48. package/scripts/bongos/seed-board-room-tasks.js +0 -196
  49. package/scripts/bongos/seed-bongos-app-tasks.js +0 -294
  50. package/scripts/bongos/seed-bongos-coreB-tranche1-tasks.js +0 -81
  51. package/scripts/bongos/seed-bongos-coreB-tranche2-tasks.js +0 -100
  52. package/scripts/bongos/seed-bongos-goal-hierarchy-tasks.js +0 -307
  53. package/scripts/bongos/seed-bongos-module-system-tasks.js +0 -346
  54. package/scripts/bongos/seed-bongos-tasks.js +0 -214
  55. package/scripts/bongos/seed-bongos-v1-seo-tasks.js +0 -104
  56. package/scripts/bongos/seed-bv1-community-tasks.js +0 -131
  57. package/scripts/bongos/seed-bv1-privacy-invites-tasks.js +0 -133
  58. package/scripts/bongos/seed-bv1-profile-page-tasks.js +0 -161
  59. package/scripts/bongos/seed-bv1-r01-delta-tasks.js +0 -154
  60. package/scripts/bongos/seed-bv2-tweak-mode-tasks.js +0 -277
  61. package/scripts/bongos/seed-gds-v4-tasks.js +0 -624
  62. package/scripts/bongos/seed-governance-tasks.js +0 -340
  63. package/scripts/bongos/seed-medusa-tasks.js +0 -440
  64. package/scripts/bongos/seed-module-upstreaming.js +0 -219
  65. package/scripts/bongos/seed-productization-tasks.js +0 -235
  66. package/scripts/bongos/seed-project-admin-console-tasks.js +0 -88
  67. package/scripts/bongos/seed-provisioning-tasks.js +0 -83
  68. package/scripts/bongos/seed-strict-versioning-tasks.js +0 -275
  69. package/scripts/bongos/seed-v2-world-tasks.js +0 -283
  70. package/scripts/gds/artifact-staleness.js +0 -7
  71. package/scripts/gds/classify-discipline.js +0 -7
  72. package/scripts/gds/codemod-rename-src-gds.js +0 -7
  73. package/scripts/gds/coreB-carve-proof.sh +0 -8
  74. package/scripts/gds/goal-names.js +0 -7
  75. package/scripts/gds/rename-goals.js +0 -7
  76. package/scripts/gds/seed-adopt-tasks.js +0 -7
  77. package/scripts/gds/seed-api-contract-goal-tasks.js +0 -7
  78. package/scripts/gds/seed-board-room-tasks.js +0 -6
  79. package/scripts/gds/seed-bongos-app-tasks.js +0 -7
  80. package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +0 -7
  81. package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +0 -7
  82. package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +0 -7
  83. package/scripts/gds/seed-bongos-module-system-tasks.js +0 -7
  84. package/scripts/gds/seed-bongos-tasks.js +0 -7
  85. package/scripts/gds/seed-bongos-v1-seo-tasks.js +0 -6
  86. package/scripts/gds/seed-bv1-community-tasks.js +0 -6
  87. package/scripts/gds/seed-bv1-privacy-invites-tasks.js +0 -6
  88. package/scripts/gds/seed-bv1-profile-page-tasks.js +0 -6
  89. package/scripts/gds/seed-bv1-r01-delta-tasks.js +0 -6
  90. package/scripts/gds/seed-bv2-tweak-mode-tasks.js +0 -6
  91. package/scripts/gds/seed-gds-v4-tasks.js +0 -7
  92. package/scripts/gds/seed-governance-tasks.js +0 -7
  93. package/scripts/gds/seed-medusa-tasks.js +0 -7
  94. package/scripts/gds/seed-module-upstreaming.js +0 -7
  95. package/scripts/gds/seed-productization-tasks.js +0 -7
  96. package/scripts/gds/seed-project-admin-console-tasks.js +0 -6
  97. package/scripts/gds/seed-provisioning-tasks.js +0 -7
  98. package/scripts/gds/seed-strict-versioning-tasks.js +0 -7
  99. package/scripts/gds/seed-v2-world-tasks.js +0 -7
  100. package/scripts/migrate_cost_ledger.sh +0 -178
  101. package/tests/artifact_staleness.mjs +0 -207
  102. package/tests/goal_names.mjs +0 -120
  103. package/tests/newcomer_generator.mjs +0 -48
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ // modules/autonomy/rank-roles.js — which builders hold a rank the governance
4
+ // backfill cannot match? (task 1002873; moved here from
5
+ // scripts/bongos/audit-rank-roles.js in task 1004878 so the server's daily check and
6
+ // the CLI share ONE definition).
7
+ //
8
+ // Pure: no I/O, no core require. The live ladder is an ARGUMENT — the poller passes
9
+ // api.LIVE_RANK_LADDER, the CLI passes db-kernel's — because a module file reaches
10
+ // core only through the doorway.
11
+
12
+ // findRanksWithoutRoles — given roster rows and the live ladder, which builders hold
13
+ // a rank that `governance_002`'s INNER JOIN cannot match?
14
+ //
15
+ // A row with NO rank at all counts: NULL fails the INNER JOIN the same way a
16
+ // legacy value does, and reads as "fine" in every UI that renders a blank.
17
+ function findRanksWithoutRoles(rows, liveRanks) {
18
+ const live = new Set(liveRanks || []);
19
+ return (Array.isArray(rows) ? rows : [])
20
+ .filter((b) => b && !live.has(b.rank))
21
+ .map((b) => ({
22
+ id: b.id != null ? String(b.id) : null,
23
+ // The login is what an operator needs to act; it is already visible to any
24
+ // caller authorised to read the roster, so this adds no exposure.
25
+ github_login: b.github_login || null,
26
+ rank: b.rank === undefined ? null : b.rank,
27
+ status: b.status || null,
28
+ }));
29
+ }
30
+
31
+ // summarize — the rank histogram, so a CLEAN run still says what it looked at.
32
+ // A green check that prints nothing is indistinguishable from one that read
33
+ // nothing, which is the failure mode this whole check is about.
34
+ function summarize(rows) {
35
+ const counts = new Map();
36
+ for (const b of Array.isArray(rows) ? rows : []) {
37
+ const key = b && b.rank ? b.rank : '(none)';
38
+ counts.set(key, (counts.get(key) || 0) + 1);
39
+ }
40
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]);
41
+ }
42
+
43
+ // ideaFor — the one idea a finding becomes, filed by either caller.
44
+ function ideaFor(offenders, { filedBy = 'scripts/bongos/audit-rank-roles.js' } = {}) {
45
+ return {
46
+ title: `[rank-roles] ${offenders.length} builder(s) on a rank with no governance role`,
47
+ bodyMd: [
48
+ `${offenders.length} builder(s) hold a rank with no matching governance_roles row.`,
49
+ '',
50
+ 'They resolve to ZERO permissions — admitted by rank threshold under requireRank,',
51
+ 'refused by the permission resolver, and given no reason (ADR 0151 fail-closed).',
52
+ '',
53
+ ...offenders.map((o) => `- builder ${o.id}${o.github_login ? ` (${o.github_login})` : ''} rank=${o.rank === null ? '(none)' : o.rank} status=${o.status || '?'}`),
54
+ '',
55
+ 'Fix: move each to a live rank through the normal rank-change path, which rotates',
56
+ 'the governance role in the same transaction. Not auto-repaired: changing a rank is',
57
+ 'an authority decision, and a sweep is the wrong actor for it.',
58
+ '',
59
+ `Filed by ${filedBy} (task 1002873).`,
60
+ ].join('\n'),
61
+ };
62
+ }
63
+
64
+ module.exports = { findRanksWithoutRoles, summarize, ideaFor };
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ // modules/autonomy/server-schedule.js — which routines the always-on server fires
4
+ // itself, and how often (task 1004878).
5
+ //
6
+ // ONE TABLE, TWO READERS THAT MUST AGREE. The pollers in ./pollers/ take their
7
+ // periods from here, and scripts/bongos/routine-schedule.js reads the same rows to
8
+ // decide whether a routine's declared `cadence:` has something that fires it. Before
9
+ // this table the only scheduler that check could see was .github/workflows/, so a
10
+ // routine fired in-process (peer-vote-tally) read as "manual only", and the routines
11
+ // nothing fired at all read the same way — the two cases were indistinguishable.
12
+ //
13
+ // Deliberately dependency-free: the merge-gate check requires this file in a plain
14
+ // checkout with no database and no module loader.
15
+
16
+ const DAY_MS = 24 * 60 * 60_000;
17
+ const WEEK_MS = 7 * DAY_MS;
18
+
19
+ // The cadence names a server row may use, and the period each one means. A subset of
20
+ // routine-schedule.js's vocabulary: nothing in-process runs more often than daily.
21
+ const CADENCE_MS = Object.freeze({ daily: DAY_MS, weekly: WEEK_MS });
22
+
23
+ const SERVER_ROUTINES = Object.freeze([
24
+ { routine: 'peer-vote-tally', cadence: 'weekly', poller: 'routine-timers' },
25
+ { routine: 'rank-roles-audit', cadence: 'daily', poller: 'daily-checks' },
26
+ { routine: 'dep-audit-nightly', cadence: 'daily', poller: 'daily-checks' },
27
+ ].map(Object.freeze));
28
+
29
+ // periodMs('rank-roles-audit') → 86400000. Throws on a routine the table does not
30
+ // carry, so a poller can never quietly fall back to a period nobody declared.
31
+ function periodMs(routine) {
32
+ const row = SERVER_ROUTINES.find((r) => r.routine === routine);
33
+ if (!row) throw new Error(`server-schedule: '${routine}' is not a server-fired routine`);
34
+ return CADENCE_MS[row.cadence];
35
+ }
36
+
37
+ module.exports = { SERVER_ROUTINES, CADENCE_MS, periodMs, DAY_MS, WEEK_MS };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.176",
3
+ "version": "1.21.177",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.176",
9
+ "version": "1.21.177",
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.21.176",
3
+ "version": "1.21.177",
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",
@@ -10308,5 +10308,19 @@
10308
10308
  "id": "1004858",
10309
10309
  "text": "The status page no longer shows an uptime panel and a \"standing\" card that only ever said \"unknown\" — the checker behind them was never switched on. The dead checker and its data feed are gone too."
10310
10310
  }
10311
+ ],
10312
+ "1.21.177": [
10313
+ {
10314
+ "id": "1004887",
10315
+ "text": "Main was failing one safety check after two cleanup changes landed together. The check's expected count now matches the files we deliberately removed, so main is green again."
10316
+ },
10317
+ {
10318
+ "id": "1004878",
10319
+ "text": "Two daily safety checks — builders on a broken rank and known-vulnerable dependencies — now run on the server by themselves and land in the ideas inbox; five dead routines were retired."
10320
+ },
10321
+ {
10322
+ "id": "1004856",
10323
+ "text": "Removed 30 old scripts that each did one job once (mostly creating batches of tasks) and were never used again — about 6,500 lines less to read and maintain. Nothing that runs today used them."
10324
+ }
10311
10325
  ]
10312
10326
  }
@@ -36,7 +36,7 @@
36
36
  // Note fitness-ratchets.js belongs here too: it is the GATE, and its builder-facing hint has
37
37
  // to spell the alias out to tell anyone what they tripped over.
38
38
  // 4. THE RENAME GUARDRAILS (path-excluded). rename-history-check.js, rename-ledger-prose.js,
39
- // gds-literal-scan.js, codemod-rename-src-gds.js, fitness-ratchets.js and THIS FILE name the
39
+ // gds-literal-scan.js, fitness-ratchets.js and THIS FILE name the
40
40
  // old path because that is what they are for. Same principle as GDS_RULE_FILES next door: a
41
41
  // gate that cannot state its own rule cannot explain itself.
42
42
  // 5. tests/ (path-excluded). Tests are what keeps the alias exercised in CI. Moving them off it
@@ -110,7 +110,7 @@ const ALIAS_EXCLUDED_PATHS = [
110
110
  // 3. the live proof that the alias still answers
111
111
  /^scripts\/(?:gds|bongos)\/smoke-gds\.js$/,
112
112
  // 4. the rename guardrails — they name the old path on purpose
113
- /^scripts\/(?:gds|bongos)\/(rename-history-check|rename-ledger-prose|gds-literal-scan|codemod-rename-src-gds|api-alias-caller-scan|fitness-ratchets)\.js$/,
113
+ /^scripts\/(?:gds|bongos)\/(rename-history-check|rename-ledger-prose|gds-literal-scan|api-alias-caller-scan|fitness-ratchets)\.js$/,
114
114
  /^config\/(fitness-baselines|rename-history-baseline)\.json$/,
115
115
  // 5. tests keep the alias exercised in CI — the repo's own two spellings, `tests/` at the root
116
116
  // and a module's `tests/` or `test/` dir. Deliberately NOT a blanket `\.mjs$`: three .mjs
@@ -196,11 +196,6 @@ ALLOWLIST=(
196
196
  # instance's owner literal precisely so packaging can SCRUB it from every
197
197
  # published core — the anti-leak spec, not a runtime hardcode (task 1002640).
198
198
  'scripts/bongos/package-core.js'
199
-
200
- # Frozen seed script — a task DESCRIPTION quotes migration 023's founding-
201
- # Archon literal as the thing that task exists to remove (the
202
- # seed-v3-tasks.js class: stripping it would lose the task history).
203
- 'scripts/bongos/seed-medusa-tasks.js'
204
199
  )
205
200
 
206
201
  # ----------------------------------------------------------------------------
@@ -21,8 +21,10 @@
21
21
  // THIS IS THE RE-RUNNABLE HALF, and it is deliberately NOT fatal at boot. Idea
22
22
  // 1000622 records that trap: an assertion that throws on startup turns a data
23
23
  // problem on one instance into an outage. This is a standalone read-only script
24
- // for a nightly/deploy-time caller — it exits 1 so a caller can act, and nothing
25
- // in the server's boot path requires it.
24
+ // for a by-hand or deploy-time caller — it exits 1 so a caller can act, and nothing
25
+ // in the server's boot path requires it. The SCHEDULED run is in-process: the
26
+ // server's daily-checks poller (task 1004878) reads the roster straight from its
27
+ // database and files the same idea, so it needs no CLI session.
26
28
  //
27
29
  // It reports rather than repairs, and that is a decision, not an omission:
28
30
  // rewriting somebody's rank is an authority change, and it belongs to an operator
@@ -36,7 +38,7 @@
36
38
  // 2 could not read the roster (no session / network / route absent)
37
39
  //
38
40
  // `--file-idea` is what makes this a SURFACE rather than another thing nobody
39
- // runs — the same shape diagram-drift/check.js uses, spawning capture.js so idea
41
+ // runs — spawning capture.js so idea
40
42
  // filing has one implementation. Without it a nightly deterministic run that exits
41
43
  // 1 reports into a log nobody reads, which is the exact criticism this task makes
42
44
  // of the status quo.
@@ -48,69 +50,26 @@ const os = require('node:os');
48
50
  const { cliClient, requireSession, cliExit } = require('./cli-lib');
49
51
  const { LIVE_RANK_LADDER } = require('../../src/bongos/db-kernel.js');
50
52
 
51
- // findRanksWithoutRoles — the PURE half. Given roster rows and the live ladder,
52
- // which builders hold a rank that `governance_002`'s INNER JOIN cannot match?
53
- //
54
- // Pure + exported because this is the only altitude the repo can test: the unit
55
- // gate has no Postgres, which is exactly why the gap this script closes went
56
- // unverified for so long. A DB-free test of the decision is not a consolation
57
- // prize here — it is the thing that can actually run in CI.
58
- //
59
- // A row with NO rank at all counts: NULL fails the INNER JOIN the same way a
60
- // legacy value does, and reads as "fine" in every UI that renders a blank.
61
- function findRanksWithoutRoles(rows, liveRanks = LIVE_RANK_LADDER) {
62
- const live = new Set(liveRanks);
63
- return (Array.isArray(rows) ? rows : [])
64
- .filter((b) => b && !live.has(b.rank))
65
- .map((b) => ({
66
- id: b.id != null ? String(b.id) : null,
67
- // The login is what an operator needs to act; it is already visible to any
68
- // caller authorised to read the roster, so this adds no exposure.
69
- github_login: b.github_login || null,
70
- rank: b.rank === undefined ? null : b.rank,
71
- status: b.status || null,
72
- }));
73
- }
53
+ // The pure half lives in modules/autonomy/rank-roles.js (task 1004878), shared with
54
+ // the server's daily check (modules/autonomy/pollers/daily-checks.js) so the two
55
+ // cannot disagree about what an offender is. This wrapper defaults the ladder.
56
+ const rankRoles = require('../../modules/autonomy/rank-roles.js');
74
57
 
75
- // summarize — the rank histogram, so a CLEAN run still says what it looked at.
76
- // A green check that prints nothing is indistinguishable from one that read
77
- // nothing, which is the failure mode this whole task is about.
78
- function summarize(rows) {
79
- const counts = new Map();
80
- for (const b of Array.isArray(rows) ? rows : []) {
81
- const key = b && b.rank ? b.rank : '(none)';
82
- counts.set(key, (counts.get(key) || 0) + 1);
83
- }
84
- return [...counts.entries()].sort((a, b) => b[1] - a[1]);
85
- }
58
+ const findRanksWithoutRoles = (rows, liveRanks = LIVE_RANK_LADDER) => rankRoles.findRanksWithoutRoles(rows, liveRanks);
59
+ const { summarize } = rankRoles;
86
60
 
87
61
  // Spawn capture.js rather than POSTing here, so idea filing keeps one
88
- // implementation (the diagram-drift/check.js precedent). Best-effort: a failure to
62
+ // implementation. Best-effort: a failure to
89
63
  // file must not turn a successful audit into a crash — the exit code and stdout
90
64
  // still carry the finding.
91
65
  function fileIdea(offenders) {
92
- const body = [
93
- `${offenders.length} builder(s) hold a rank with no matching governance_roles row.`,
94
- '',
95
- 'They resolve to ZERO permissions — admitted by rank threshold under requireRank,',
96
- 'refused by the permission resolver, and given no reason (ADR 0151 fail-closed).',
97
- '',
98
- ...offenders.map((o) => `- builder ${o.id}${o.github_login ? ` (${o.github_login})` : ''} rank=${o.rank === null ? '(none)' : o.rank} status=${o.status || '?'}`),
99
- '',
100
- 'Fix: move each to a live rank through the normal rank-change path, which rotates',
101
- 'the governance role in the same transaction. Not auto-repaired: changing a rank is',
102
- 'an authority decision, and a sweep is the wrong actor for it.',
103
- '',
104
- 'Filed by scripts/bongos/audit-rank-roles.js (task 1002873).',
105
- ].join('\n');
66
+ const { title, bodyMd } = rankRoles.ideaFor(offenders);
106
67
  const tmp = path.join(os.tmpdir(), `rank-roles-audit-${process.pid}.md`);
107
68
  try {
108
- fs.writeFileSync(tmp, body, 'utf8');
69
+ fs.writeFileSync(tmp, bodyMd, 'utf8');
109
70
  const r = spawnSync(
110
71
  process.execPath,
111
- [path.join(__dirname, 'capture.js'),
112
- `[rank-roles] ${offenders.length} builder(s) on a rank with no governance role`,
113
- '--kind', 'bug', '--body-file', tmp],
72
+ [path.join(__dirname, 'capture.js'), title, '--kind', 'bug', '--body-file', tmp],
114
73
  { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000 }
115
74
  );
116
75
  if (r.status !== 0) console.error('audit-rank-roles: capture.js failed to file the idea — the exit code above still carries the finding.');
@@ -81,7 +81,6 @@ const EXCLUDE = new Set([
81
81
  'tests/scripts_gds_forwarders.mjs',
82
82
  'modules/lifecycle/executed-closure.js',
83
83
  'scripts/bongos/rename-ledger-prose.js',
84
- 'scripts/bongos/codemod-rename-src-gds.js',
85
84
  '.bongos-core.json',
86
85
  ]);
87
86
 
@@ -1,7 +1,7 @@
1
1
  // Shared fact-extractor for the onboarding diagrams (task #357).
2
2
  //
3
3
  // Single source of truth for "what the live system actually is", used by BOTH:
4
- // - the drift checker (.claude/scheduled-tasks/diagram-drift/check.js), which
4
+ // - (formerly) the drift checker .claude/scheduled-tasks/diagram-drift/check.js, retired in task 1004878, which
5
5
  // compares these facts against assertions.json and nudges on divergence; and
6
6
  // - the generator (scripts/bongos/gen-diagrams.js), which regenerates the
7
7
  // mechanical diagrams (.mmd) FROM these facts on every deploy.
@@ -15,7 +15,7 @@
15
15
  // logs the call it WOULD make and returns a synthetic, clearly-marked
16
16
  // result (`{ dryRun: true, ... }`) so the orchestrator's control flow runs
17
17
  // end-to-end against a throwaway plan. This mirrors how provision-staging.sh
18
- // and stale-claim-release ship runnable logic whose LIVE leg is a separate,
18
+ // ships runnable logic whose LIVE leg is a separate,
19
19
  // explicit operator action.
20
20
  // - Live mode requires a token (DO_API_TOKEN); the constructor throws clearly
21
21
  // if asked to run live without one — it never silently no-ops a real call.
@@ -79,10 +79,8 @@ const IDENTITY_SCAN_EXTRA_FILES = [];
79
79
  // * backfill-sessions.js / version-close.js — one-time operator runbook text
80
80
  // naming the actual prod DB + a historical local directory. Describing what
81
81
  // an operator must literally type is the correct content.
82
- // * seed-*.js — the hits are PROSE INSIDE seeded task descriptions: historical
83
- // records of what those tasks said when they were filed. Rewriting them would
84
- // falsify the record; deleting the files would destroy it. They are inert
85
- // (idempotent on source_ref) and referenced only by generated manifests.
82
+ // * seed-*.js — the nine one-shot seeders once listed here (prose inside seeded
83
+ // task descriptions) were deleted by task 1004856; the repo history keeps them.
86
84
  // * the four *.sh entries — surfaced when `.sh` joined the extension set (task
87
85
  // 1002567). Each is a HOST-PINNED OPS script, not portable code: they name the
88
86
  // live box's actual Postgres database/role and its actual /etc secrets dir, and
@@ -94,7 +92,7 @@ const IDENTITY_SCAN_EXTRA_FILES = [];
94
92
  // NEW literal in any of them fails exactly like one in an unlisted file.
95
93
  // * fitness-checks-identity.js / package-core.js — THE DETECTORS THEMSELVES: Check 13's regexes
96
94
  // and package-core's PRIOR_INSTANCE_FLOOR name the brand because hunting it is
97
- // their job. * seed-v3-tasks.js — the seeded-task-prose rationale above. All
95
+ // their job. (seed-v3-tasks.js was the third; it is gone.) All
98
96
  // three passed pre-1002756 only by the old stripper's accidents (phantom
99
97
  // blocks + regex-blindness); now they are seen and counted.
100
98
  const IDENTITY_SCAN_ALLOWED = {
@@ -113,19 +111,10 @@ const IDENTITY_SCAN_ALLOWED = {
113
111
  'scripts/bongos/seed-staging.sh': 4,
114
112
  'scripts/bongos/backfill-sessions.js': 2,
115
113
  'scripts/bongos/publish-manifest.js': 3,
116
- 'scripts/bongos/seed-bongos-coreB-tranche1-tasks.js': 1,
117
- 'scripts/bongos/seed-bongos-coreB-tranche2-tasks.js': 1,
118
- 'scripts/bongos/seed-bongos-goal-hierarchy-tasks.js': 1,
119
- 'scripts/bongos/seed-bongos-tasks.js': 1,
120
- 'scripts/bongos/seed-gds-v4-tasks.js': 1,
121
- 'scripts/bongos/seed-medusa-tasks.js': 7,
122
- 'scripts/bongos/seed-module-upstreaming.js': 1,
123
- 'scripts/bongos/seed-productization-tasks.js': 6,
124
- 'scripts/bongos/seed-provisioning-tasks.js': 2,
125
114
  'scripts/bongos/version-close.js': 1,
126
115
  // task 1002853 (the company literal joined the list): audit-authorship.sh is itself a
127
116
  // detector — its ops allowlist names the founder's commit address so it can exempt it; grade-smoke.js
128
- // is a protected grader probe whose fixture task prose names the company, the seed-*.js rationale.
117
+ // is a protected grader probe whose fixture task prose names the company.
129
118
  'scripts/bongos/audit-authorship.sh': 1,
130
119
  'scripts/bongos/grade-smoke.js': 1,
131
120
  };
@@ -488,7 +488,7 @@ function rebuildAssertions(prev, f) {
488
488
  next.architecture = {
489
489
  diagram: '04-architecture',
490
490
  _comment:
491
- "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, so this is the drift checker's backstop. 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.",
491
+ "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.",
492
492
  live_tasks: arch.live_tasks,
493
493
  pending_tasks: arch.pending_tasks,
494
494
  };
@@ -499,7 +499,7 @@ function rebuildAssertions(prev, f) {
499
499
  ...next.drachmae_karma,
500
500
  diagram: '03-drachmae-karma',
501
501
  _comment:
502
- "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, so this slice is a redundant backstop the drift checker verifies. 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/.",
502
+ "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/.",
503
503
  };
504
504
  next.drachmae_karma.live_tasks = dk.live_tasks;
505
505
  next.drachmae_karma.pending_tasks = dk.pending_tasks;
@@ -589,7 +589,7 @@ const CONCEPTUAL_TARGETS = [
589
589
  // r.error (ENOENT) and r.status is null. The .mmd sources are already
590
590
  // written above and the caller (ship.js regenerateDiagrams) will commit
591
591
  // them; PNGs get re-rendered on the Linux droplet at deploy time, and the
592
- // diagram-drift routine backstops any staleness. So skip the render
592
+ // next render-capable ship refreshes them. So skip the render
593
593
  // NON-FATALLY rather than hard-failing the whole ship on a missing bash.
594
594
  console.warn(
595
595
  `gen-diagrams: could not run the render step (${r.error.code || r.error.message}). ` +
@@ -8,9 +8,11 @@
8
8
  // node scripts/bongos/pulse.js --claim-id <N>
9
9
  //
10
10
  // 2) Release-stale mode (V3.R49 / #268) — scan for active claims with
11
- // no pulse in N hours and auto-release them. Used by the
12
- // `stale-claim-release` scheduled task (runs every 15 min on the
13
- // droplet) and runnable by hand for ops.
11
+ // no pulse in N hours and auto-release them. Runnable by hand for ops
12
+ // ONLY: the scheduled `stale-claim-release` routine was retired unscheduled
13
+ // in task 1004878, because nothing records pulses yet (no POST /pulse route,
14
+ // no hook sends one), so a timed sweep would release every claim older
15
+ // than the threshold.
14
16
  // node scripts/bongos/pulse.js --release-stale [--threshold-hours 6] [--dry-run]
15
17
  //
16
18
  // -------------------------------------------------------------------------
@@ -51,6 +51,9 @@ const TASKS_DIR = path.join(ROOT, '.claude', 'scheduled-tasks');
51
51
  const WORKFLOWS_DIR = path.join(ROOT, '.github', 'workflows');
52
52
 
53
53
  const { parseRoutineFrontmatter } = require('./run-routine.js');
54
+ // The routines the always-on server fires itself (task 1004878) — the second
55
+ // scheduler this check reads, beside .github/workflows/.
56
+ const { SERVER_ROUTINES } = require('../../modules/autonomy/server-schedule.js');
54
57
 
55
58
  // The NAMED cadences a routine may declare. The value is the period a cron
56
59
  // expression must resolve to for the two to agree. Add a name here and
@@ -189,17 +192,32 @@ function readWorkflows(dir = WORKFLOWS_DIR) {
189
192
  // `workflows === null` means "this checkout has no CI to schedule with", which
190
193
  // downgrades unscheduled routines from violations to warnings (see the two-repo
191
194
  // note in the header).
192
- function evaluate({ withCadence, withoutCadence }, workflows) {
195
+ //
196
+ // `server` is the in-process scheduler (modules/autonomy/server-schedule.js): rows
197
+ // of { routine, cadence } the always-on server fires itself. A row there IS a
198
+ // scheduler, at exactly its cadence — and it travels with the core, so it fires in
199
+ // an instance checkout too, where there are no workflows. It is held to the same
200
+ // rules as a cron: the routine must declare the cadence the server realizes, and
201
+ // must still exist.
202
+ function evaluate({ withCadence, withoutCadence }, workflows, server = []) {
193
203
  const violations = [];
194
204
  const warnings = [];
195
205
  const hasCI = workflows !== null;
196
206
  const wf = workflows || [];
207
+ const serverRow = new Map(server.map((s) => [s.routine, s]));
197
208
 
198
209
  for (const r of withCadence) {
199
210
  if (!isCadence(r.cadence)) {
200
211
  violations.push(`routine '${r.name}': unknown cadence "${r.cadence}" (want ${[...CADENCES].join('|')}, or <N>min where N divides 60)`);
201
212
  continue;
202
213
  }
214
+ const srv = serverRow.get(r.name);
215
+ if (srv) {
216
+ if (srv.cadence !== r.cadence) {
217
+ violations.push(`routine '${r.name}' declares cadence: ${r.cadence} but the server fires it ${srv.cadence} (modules/autonomy/server-schedule.js)`);
218
+ }
219
+ continue;
220
+ }
203
221
  const firing = wf.filter((w) => invokesRoutine(w.text, r.name, r.steps));
204
222
  if (!firing.length) {
205
223
  const msg = `routine '${r.name}' declares cadence: ${r.cadence} but NOTHING fires it — no scheduled workflow invokes it`;
@@ -242,7 +260,24 @@ function evaluate({ withCadence, withoutCadence }, workflows) {
242
260
  }
243
261
  violations.push(...dangling);
244
262
 
263
+ // The server-side rename rot, and a label that lies the other way: a routine the
264
+ // server fires on a schedule its own SKILL.md says nothing about. Skipped where
265
+ // there is no routine tree at all (an instance before materialization) — there
266
+ // every row would read as dangling, and none of them is.
267
+ const serverFired = new Set();
268
+ if (known.size) {
269
+ for (const s of server) {
270
+ if (!known.has(s.routine)) {
271
+ violations.push(`modules/autonomy/server-schedule.js fires routine '${s.routine}', which has no .claude/scheduled-tasks/${s.routine}/SKILL.md`);
272
+ } else if (withoutCadence.some((r) => r.name === s.routine)) {
273
+ violations.push(`routine '${s.routine}' is fired ${s.cadence} by the server but its SKILL.md declares no cadence: — add \`cadence: ${s.cadence}\``);
274
+ serverFired.add(s.routine);
275
+ }
276
+ }
277
+ }
278
+
245
279
  for (const r of withoutCadence) {
280
+ if (serverFired.has(r.name)) continue;
246
281
  warnings.push(`routine '${r.name}' declares no cadence: — it can only ever be run by hand`);
247
282
  }
248
283
  return { violations, warnings };
@@ -252,7 +287,7 @@ function evaluate({ withCadence, withoutCadence }, workflows) {
252
287
  function checkRoutineCadencesScheduled() {
253
288
  const routines = readRoutines();
254
289
  const workflows = readWorkflows();
255
- const { violations, warnings } = evaluate(routines, workflows);
290
+ const { violations, warnings } = evaluate(routines, workflows, SERVER_ROUTINES);
256
291
  const scheduled = routines.withCadence.length - violations.filter((v) => v.startsWith("routine '")).length;
257
292
  return {
258
293
  name: 'a declared cron cadence has something that fires it (task 1003209)',
@@ -262,7 +297,7 @@ function checkRoutineCadencesScheduled() {
262
297
  warnings,
263
298
  note: violations.length
264
299
  ? 'a scheduled routine is declared but dormant — add/repair the scheduler, or drop the cadence: field and stop claiming it runs'
265
- : `${scheduled}/${routines.withCadence.length} cadence-declaring routine(s) fired by a committed scheduler at the declared period; ${routines.withoutCadence.length} manual-only.`,
300
+ : `${scheduled}/${routines.withCadence.length} cadence-declaring routine(s) fired by a committed scheduler (a workflow or the server) at the declared period; ${routines.withoutCadence.length} manual-only.`,
266
301
  };
267
302
  }
268
303
 
@@ -22,7 +22,7 @@ function currentBranch() {
22
22
  // Task #357: keep the 3 mechanical onboarding diagrams current on every deploy.
23
23
  // Regenerate them from live facts; if any changed, commit the refresh on this
24
24
  // branch so it ships with the deploy. Non-fatal — a render/network hiccup must
25
- // never block a ship; the diagram-drift routine will flag any miss.
25
+ // never block a ship; the next render-capable ship refreshes any miss.
26
26
  //
27
27
  // Returns a status string ('skipped-server' | 'regen-failed' | 'current' |
28
28
  // 'committed' | 'committed-nofatal') so the regression test (task 1046) can
@@ -35,8 +35,7 @@ function regenerateDiagrams() {
35
35
  // leaving those files UNCOMMITTED in the builder's tree (had to be reverted by
36
36
  // hand). That path must not attempt the regen at all: skip here and let a
37
37
  // render-capable ship (the laptop path, which CAN render the PNGs) refresh them
38
- // as a matched .mmd+.png+assertions set; the diagram-drift routine backstops any
39
- // staleness in the meantime. Skipping entirely (vs --no-render) avoids committing
38
+ // as a matched .mmd+.png+assertions set. Skipping entirely (vs --no-render) avoids committing
40
39
  // a .mmd/.png mismatch to main. Mirrors gen-diagrams.js's own non-fatal skip when
41
40
  // bash itself is absent (ENOENT).
42
41
  if (pushVia() === 'server') {