@bongos/core 1.21.63 → 1.21.65

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.
@@ -5,7 +5,7 @@
5
5
  "adr": "0341 D3",
6
6
  "reread": "node scripts/gds/page-reader.js --stale",
7
7
  "how": ["Generated by node scripts/gds/page-reader.js (BV2.TW04, task 1004315; ADR 0341 D3). Do not hand-edit.","One entry per page of docs/page-inventory.json. Lines are in page order: the default state first, and a line only another state shows sits where that state shows it, with its state named.","placement: placed = a docs/copy-registry.json row renders it (file, line, string_id); shared = shell text every page of the surface carries (top bar, sidebar, footer), with its row when the registry has one; unplaced = the registry cannot place it (a rewrite becomes an engineer task, ADR 0341 D4).","text keeps {…} where the registry row has a hole, or where a value the page read from its API was filled in. A string that is only such data is counted under omitted.data, not listed.","reading_hash is over the non-shared lines (key, section, text); files_hash is over the page's inventory files. --check compares files_hash only and never opens a browser.","Rendered through the hall-preview harness (hall) and the ui-design kit stub (landing, status), at 1280x800 with the clock pinned, so a reading is the fixture world's page."],
8
- "counts": {"pages":43,"lines":5036,"placed":1590,"shared":395,"unplaced":3051,"omitted_data":2497},
8
+ "counts": {"pages":43,"lines":5044,"placed":1595,"shared":395,"unplaced":3054,"omitted_data":2513},
9
9
  "pages": [
10
10
  {
11
11
  "id": "builders:atlas",
@@ -3823,10 +3823,10 @@
3823
3823
  "surface": "builders",
3824
3824
  "title": "Watch",
3825
3825
  "states_read": ["board","out","operations","security"],
3826
- "files_hash": "7194297c3ec32dce",
3827
- "reading_hash": "d2d6b075e14c0ad2",
3828
- "counts": {"lines":74,"placed":27,"shared":9,"unplaced":38},
3829
- "omitted": {"data":52},
3826
+ "files_hash": "0b7766cc6b8efa03",
3827
+ "reading_hash": "075b05aa7b88e75b",
3828
+ "counts": {"lines":82,"placed":32,"shared":9,"unplaced":41},
3829
+ "omitted": {"data":68},
3830
3830
  "notes": ["operations: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","security: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood"],
3831
3831
  "lines": [
3832
3832
  {"key":"L0001","section":"skip link","text":"Skip to content","placement":"shared","file":"modules/hall-ui/public/shell.js","line":811,"string_id":"87cee78ca134"},
@@ -3838,71 +3838,79 @@
3838
3838
  {"key":"L0007","section":"top bar","text":"Ideating","placement":"shared"},
3839
3839
  {"key":"L0008","section":"top bar","text":"Governing","placement":"shared"},
3840
3840
  {"key":"L0009","section":"Watch","text":"Watch","placement":"unplaced"},
3841
- {"key":"L0010","section":"Watch","text":"System oversight: people, operations and security.","placement":"placed","file":"modules/hall-ui/public/watch.js","line":1364,"string_id":"4894b1d9b0de"},
3842
- {"key":"L0011","section":"Watch","text":"Access requests","placement":"placed","file":"modules/hall-ui/public/watch.html","line":46,"string_id":"996edffc1966"},
3843
- {"key":"L0012","section":"Watch","text":"pending, on the People tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":46,"string_id":"2fb1dad96280"},
3844
- {"key":"L0013","section":"Watch","text":"Security reports","placement":"placed","file":"modules/hall-ui/public/watch.html","line":47,"string_id":"ab8af800f2bb"},
3845
- {"key":"L0014","section":"Watch","text":"open, on the Security tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":47,"string_id":"4f279d68175f"},
3846
- {"key":"L0015","section":"Watch","text":"Stale claims","placement":"placed","file":"modules/hall-ui/public/watch.html","line":48,"string_id":"f2a4d855d82d"},
3847
- {"key":"L0016","section":"Watch","text":"builders, on the People tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":48,"string_id":"552e00198649"},
3841
+ {"key":"L0010","section":"Watch","text":"System oversight: people, operations and security.","placement":"placed","file":"modules/hall-ui/public/watch.js","line":1450,"string_id":"4894b1d9b0de"},
3842
+ {"key":"L0011","section":"Watch","text":"Access requests","placement":"placed","file":"modules/hall-ui/public/watch.html","line":49,"string_id":"996edffc1966"},
3843
+ {"key":"L0012","section":"Watch","text":"pending, on the People tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":49,"string_id":"2fb1dad96280"},
3844
+ {"key":"L0013","section":"Watch","text":"Security reports","placement":"placed","file":"modules/hall-ui/public/watch.html","line":50,"string_id":"ab8af800f2bb"},
3845
+ {"key":"L0014","section":"Watch","text":"open, on the Security tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":50,"string_id":"4f279d68175f"},
3846
+ {"key":"L0015","section":"Watch","text":"Stale claims","placement":"placed","file":"modules/hall-ui/public/watch.html","line":51,"string_id":"f2a4d855d82d"},
3847
+ {"key":"L0016","section":"Watch","text":"builders, on the People tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":51,"string_id":"552e00198649"},
3848
3848
  {"key":"L0017","section":"Watch","text":"Sessions","placement":"unplaced"},
3849
- {"key":"L0018","section":"Watch","text":"recorded, on the Operations tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":49,"string_id":"f83f0a9d6b6a"},
3849
+ {"key":"L0018","section":"Watch","text":"recorded, on the Operations tab","placement":"placed","file":"modules/hall-ui/public/watch.html","line":52,"string_id":"f83f0a9d6b6a"},
3850
3850
  {"key":"L0019","section":"Watch","text":"People","placement":"unplaced"},
3851
3851
  {"key":"L0020","section":"Watch","text":"Operations","placement":"unplaced"},
3852
3852
  {"key":"L0021","section":"Watch","text":"Security","placement":"unplaced"},
3853
- {"key":"L0022","section":"Access requests","text":"Access requests","placement":"placed","file":"modules/hall-ui/public/watch.html","line":46,"string_id":"996edffc1966"},
3854
- {"key":"L0023","section":"Access requests","text":"People asking to join. Approve or dismiss each request, re-read the ones you already decided, and take back an approval before that person has signed in.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":62,"string_id":"a69aff719f1a"},
3853
+ {"key":"L0022","section":"Access requests","text":"Access requests","placement":"placed","file":"modules/hall-ui/public/watch.html","line":49,"string_id":"996edffc1966"},
3854
+ {"key":"L0023","section":"Access requests","text":"People asking to join. Approve or dismiss each request, re-read the ones you already decided, and take back an approval before that person has signed in.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":65,"string_id":"a69aff719f1a"},
3855
3855
  {"key":"L0024","section":"Access requests","text":"Invite by GitHub username","placement":"placed","file":"modules/hall-ui/public/watch.js","line":295,"string_id":"24f9e88c21f6"},
3856
3856
  {"key":"L0025","section":"Access requests","text":"e.g. octocat","placement":"placed","file":"modules/hall-ui/public/watch.js","line":296,"string_id":"77b0b2b446a1","attr":"placeholder"},
3857
- {"key":"L0026","section":"Access requests","text":"Invite","placement":"unplaced","occurrences":3},
3857
+ {"key":"L0026","section":"Access requests","text":"Invite","placement":"unplaced","occurrences":7},
3858
3858
  {"key":"L0027","section":"Access requests","text":"Admit someone before they ask — once invited, their sign-in gets in.","placement":"unplaced"},
3859
3859
  {"key":"L0028","section":"Access requests","text":"Show","placement":"placed","file":"modules/hall-ui/public/profile.js","line":793,"string_id":"a81ec73539ef"},
3860
3860
  {"key":"L0029","section":"Access requests","text":"Waiting","placement":"unplaced"},
3861
3861
  {"key":"L0030","section":"Access requests","text":"Admitted","placement":"unplaced"},
3862
3862
  {"key":"L0031","section":"Access requests","text":"Declined","placement":"unplaced"},
3863
- {"key":"L0032","section":"Access requests","text":"Name unvouched","placement":"placed","file":"modules/hall-ui/public/watch.js","line":397,"string_id":"5e8199266a8a","occurrences":2},
3864
- {"key":"L0033","section":"Access requests","text":"Asked {…}","placement":"placed","file":"modules/hall-ui/public/watch.js","line":517,"string_id":"b2656d0b2402","occurrences":2},
3865
- {"key":"L0034","section":"Access requests","text":"Dismiss","placement":"unplaced","occurrences":2},
3866
- {"key":"L0035","section":"Roster","text":"Roster","placement":"unplaced"},
3867
- {"key":"L0036","section":"Roster","text":"Every builder in {{worldName}} — rank, {{currency}}, karma, and active claims.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":69,"string_id":"8690c6424585"},
3868
- {"key":"L0037","section":"Roster","text":"Builder","placement":"unplaced"},
3869
- {"key":"L0038","section":"Roster","text":"Rank","placement":"unplaced"},
3870
- {"key":"L0039","section":"Roster","text":"Credits","placement":"unplaced"},
3871
- {"key":"L0040","section":"Roster","text":"Karma","placement":"unplaced"},
3872
- {"key":"L0041","section":"Roster","text":"Active claims","placement":"unplaced"},
3873
- {"key":"L0042","section":"Roster","text":"Standing","placement":"unplaced"},
3874
- {"key":"L0043","section":"Roster","text":"Action","placement":"unplaced"},
3875
- {"key":"L0044","section":"Roster","text":"Offboard","placement":"unplaced","occurrences":3},
3876
- {"key":"L0045","section":"Roster","text":"stale","placement":"unplaced"},
3877
- {"key":"L0046","section":"Roster","text":"That's you","placement":"placed","file":"modules/hall-ui/public/watch.js","line":832,"string_id":"6e5a324b2972"},
3878
- {"key":"L0047","section":"Roster","text":"Reactivate","placement":"unplaced"},
3879
- {"key":"L0048","section":"Roster","text":"Your own row carries no controls. Nobody can change their own rank or standing — that guard is what stops the last {…} locking everyone out — so another {…} does it.","placement":"placed","file":"modules/hall-ui/public/watch.js","line":864,"string_id":"5e0ec4cd1cfe"},
3880
- {"key":"L0049","section":"Reviews by builder","text":"Reviews by builder","placement":"placed","file":"modules/hall-ui/public/watch.html","line":75,"string_id":"4e062d4f8d69"},
3881
- {"key":"L0050","section":"Reviews by builder","text":"How each builder's shipped work has been graded over the past thirty days.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":76,"string_id":"0bab9286ea38"},
3882
- {"key":"L0051","section":"Reviews by builder","text":"Builder","placement":"unplaced"},
3883
- {"key":"L0052","section":"Reviews by builder","text":"Graded","placement":"unplaced"},
3884
- {"key":"L0053","section":"Reviews by builder","text":"Avg score","placement":"unplaced"},
3885
- {"key":"L0054","section":"Reviews by builder","text":"Pass rate","placement":"unplaced"},
3886
- {"key":"L0055","section":"Reviews by builder","text":"Last graded","placement":"unplaced"},
3887
- {"key":"L0056","section":"Reviews by builder","text":"Window: last 30 days.","placement":"unplaced"},
3888
- {"key":"L0057","section":"Review quality","text":"Review quality","placement":"placed","file":"modules/hall-ui/public/watch.html","line":82,"string_id":"f1b2b158c3b1"},
3889
- {"key":"L0058","section":"Review quality","text":"Grades across all recent shipped work — a rolling aggregate.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":83,"string_id":"24d58d284952"},
3890
- {"key":"L0059","section":"Review quality","text":"Avg score","placement":"unplaced"},
3891
- {"key":"L0060","section":"Review quality","text":"Pass rate","placement":"unplaced"},
3892
- {"key":"L0061","section":"Review quality","text":"Sample size","placement":"unplaced"},
3893
- {"key":"L0062","section":"Review quality","text":"Threshold","placement":"unplaced"},
3894
- {"key":"L0063","section":"Review quality","text":"Per day: ships graded · avg score.","placement":"unplaced"},
3895
- {"key":"L0064","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":471,"string_id":"1883ad7f07fc"},
3896
- {"key":"L0065","section":"Review quality","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":352,"string_id":"fd1903586e8e"},
3897
- {"key":"L0066","section":"What are you doing now?","text":"What are you doing now?","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":353,"string_id":"60853319f290"},
3898
- {"key":"L0067","section":"What are you doing now?","text":"Pick one. You can switch any time from the top bar.","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":354,"string_id":"4f87d5b17b54"},
3899
- {"key":"L0068","section":"What are you doing now?","text":"Building","placement":"unplaced"},
3900
- {"key":"L0069","section":"What are you doing now?","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":3},
3901
- {"key":"L0070","section":"What are you doing now?","text":"Artistry","placement":"unplaced"},
3902
- {"key":"L0071","section":"What are you doing now?","text":"Ideating","placement":"unplaced"},
3903
- {"key":"L0072","section":"What are you doing now?","text":"Turn thoughts into ideas.","placement":"unplaced"},
3904
- {"key":"L0073","section":"What are you doing now?","text":"Governing","placement":"unplaced"},
3905
- {"key":"L0074","section":"What are you doing now?","text":"Ask me again next session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":357,"string_id":"fa86bf403775"}
3863
+ {"key":"L0032","section":"Access requests","text":"Invite all 3","placement":"unplaced"},
3864
+ {"key":"L0033","section":"Access requests","text":"{…} project{…}","placement":"placed","file":"modules/hall-ui/public/fleet.js","line":39,"string_id":"771bef3d31b5"},
3865
+ {"key":"L0034","section":"Access requests","text":"Last {…} {…}, {…}","placement":"unplaced"},
3866
+ {"key":"L0035","section":"Access requests","text":"Identity vouched","placement":"placed","file":"modules/hall-ui/public/watch.js","line":396,"string_id":"9e9f8a44276b","occurrences":4},
3867
+ {"key":"L0036","section":"Access requests","text":"Asked {…}","placement":"placed","file":"modules/hall-ui/public/watch.js","line":517,"string_id":"b2656d0b2402","occurrences":6},
3868
+ {"key":"L0037","section":"Access requests","text":"Dismiss","placement":"unplaced","occurrences":6},
3869
+ {"key":"L0038","section":"Access requests","text":"Active recently","placement":"placed","file":"modules/hall-ui/public/watch.js","line":458,"string_id":"74cc57f76b42"},
3870
+ {"key":"L0039","section":"Access requests","text":"Private account — this is all they share by applying.","placement":"placed","file":"modules/hall-ui/public/watch.js","line":459,"string_id":"92d8fad6dc91","occurrences":2},
3871
+ {"key":"L0040","section":"Access requests","text":"This builder hides their cross-project record.","placement":"placed","file":"modules/hall-ui/public/watch.js","line":440,"string_id":"b7c288328f79"},
3872
+ {"key":"L0041","section":"Access requests","text":"On their own","placement":"unplaced"},
3873
+ {"key":"L0042","section":"Access requests","text":"Name unvouched","placement":"placed","file":"modules/hall-ui/public/watch.js","line":397,"string_id":"5e8199266a8a","occurrences":2},
3874
+ {"key":"L0043","section":"Roster","text":"Roster","placement":"unplaced"},
3875
+ {"key":"L0044","section":"Roster","text":"Every builder in {{worldName}} — rank, {{currency}}, karma, and active claims.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":72,"string_id":"8690c6424585"},
3876
+ {"key":"L0045","section":"Roster","text":"Builder","placement":"unplaced"},
3877
+ {"key":"L0046","section":"Roster","text":"Rank","placement":"unplaced"},
3878
+ {"key":"L0047","section":"Roster","text":"Credits","placement":"unplaced"},
3879
+ {"key":"L0048","section":"Roster","text":"Karma","placement":"unplaced"},
3880
+ {"key":"L0049","section":"Roster","text":"Active claims","placement":"unplaced"},
3881
+ {"key":"L0050","section":"Roster","text":"Standing","placement":"unplaced"},
3882
+ {"key":"L0051","section":"Roster","text":"Action","placement":"unplaced"},
3883
+ {"key":"L0052","section":"Roster","text":"Offboard","placement":"unplaced","occurrences":3},
3884
+ {"key":"L0053","section":"Roster","text":"stale","placement":"unplaced"},
3885
+ {"key":"L0054","section":"Roster","text":"That's you","placement":"placed","file":"modules/hall-ui/public/watch.js","line":918,"string_id":"6e5a324b2972"},
3886
+ {"key":"L0055","section":"Roster","text":"Reactivate","placement":"unplaced"},
3887
+ {"key":"L0056","section":"Roster","text":"Your own row carries no controls. Nobody can change their own rank or standing — that guard is what stops the last {…} locking everyone out — so another {…} does it.","placement":"placed","file":"modules/hall-ui/public/watch.js","line":950,"string_id":"5e0ec4cd1cfe"},
3888
+ {"key":"L0057","section":"Reviews by builder","text":"Reviews by builder","placement":"placed","file":"modules/hall-ui/public/watch.html","line":78,"string_id":"4e062d4f8d69"},
3889
+ {"key":"L0058","section":"Reviews by builder","text":"How each builder's shipped work has been graded over the past thirty days.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":79,"string_id":"0bab9286ea38"},
3890
+ {"key":"L0059","section":"Reviews by builder","text":"Builder","placement":"unplaced"},
3891
+ {"key":"L0060","section":"Reviews by builder","text":"Graded","placement":"unplaced"},
3892
+ {"key":"L0061","section":"Reviews by builder","text":"Avg score","placement":"unplaced"},
3893
+ {"key":"L0062","section":"Reviews by builder","text":"Pass rate","placement":"unplaced"},
3894
+ {"key":"L0063","section":"Reviews by builder","text":"Last graded","placement":"unplaced"},
3895
+ {"key":"L0064","section":"Reviews by builder","text":"Window: last 30 days.","placement":"unplaced"},
3896
+ {"key":"L0065","section":"Review quality","text":"Review quality","placement":"placed","file":"modules/hall-ui/public/watch.html","line":85,"string_id":"f1b2b158c3b1"},
3897
+ {"key":"L0066","section":"Review quality","text":"Grades across all recent shipped work — a rolling aggregate.","placement":"placed","file":"modules/hall-ui/public/watch.html","line":86,"string_id":"24d58d284952"},
3898
+ {"key":"L0067","section":"Review quality","text":"Avg score","placement":"unplaced"},
3899
+ {"key":"L0068","section":"Review quality","text":"Pass rate","placement":"unplaced"},
3900
+ {"key":"L0069","section":"Review quality","text":"Sample size","placement":"unplaced"},
3901
+ {"key":"L0070","section":"Review quality","text":"Threshold","placement":"unplaced"},
3902
+ {"key":"L0071","section":"Review quality","text":"Per day: ships graded · avg score.","placement":"unplaced"},
3903
+ {"key":"L0072","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":471,"string_id":"1883ad7f07fc"},
3904
+ {"key":"L0073","section":"Review quality","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":352,"string_id":"fd1903586e8e"},
3905
+ {"key":"L0074","section":"What are you doing now?","text":"What are you doing now?","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":353,"string_id":"60853319f290"},
3906
+ {"key":"L0075","section":"What are you doing now?","text":"Pick one. You can switch any time from the top bar.","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":354,"string_id":"4f87d5b17b54"},
3907
+ {"key":"L0076","section":"What are you doing now?","text":"Building","placement":"unplaced"},
3908
+ {"key":"L0077","section":"What are you doing now?","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":3},
3909
+ {"key":"L0078","section":"What are you doing now?","text":"Artistry","placement":"unplaced"},
3910
+ {"key":"L0079","section":"What are you doing now?","text":"Ideating","placement":"unplaced"},
3911
+ {"key":"L0080","section":"What are you doing now?","text":"Turn thoughts into ideas.","placement":"unplaced"},
3912
+ {"key":"L0081","section":"What are you doing now?","text":"Governing","placement":"unplaced"},
3913
+ {"key":"L0082","section":"What are you doing now?","text":"Ask me again next session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":357,"string_id":"fa86bf403775"}
3906
3914
  ]
3907
3915
  },
3908
3916
  {
@@ -79,6 +79,36 @@ async function countRecentDiscordBugFilings(builderId, sinceIso) {
79
79
  return rows[0].n;
80
80
  }
81
81
 
82
+ // --- the #support answerer's ledger (task 1002534, ADR 0367) -----------------
83
+ //
84
+ // The bot's own builders row (migration 067, system_role='discord_bot'): the
85
+ // principal an answer is booked to. The asker is never looked up.
86
+ async function getBotPrincipalId() {
87
+ const { rows } = await pool.query(
88
+ `SELECT id FROM builders WHERE system_role = 'discord_bot' ORDER BY id LIMIT 1`
89
+ );
90
+ return rows.length ? Number(rows[0].id) : null;
91
+ }
92
+
93
+ // Month-to-date #support spend, the UTC calendar month, summed from the cost each
94
+ // answer's audit row carries. The monthly cap reads this, so it survives a
95
+ // restart. Throws on a DB error; the caller treats that as "no answer".
96
+ // INDEXED: `route = $1 AND recorded_at >= <month start>` is exactly the shape of
97
+ // migrations/033_audit_log.sql's idx_audit_log_route (route, recorded_at DESC), so
98
+ // this reads one month of one route's rows, never a scan of the whole log. It
99
+ // runs once per answer, after the free checks (rate limit, cap, concurrency).
100
+ async function sumSupportSpendThisMonth(route = 'discord:support') {
101
+ const { rows } = await pool.query(
102
+ `SELECT COALESCE(SUM((request_body_redacted->>'cost_usd')::numeric), 0)::float8 AS usd
103
+ FROM audit_log
104
+ WHERE route = $1
105
+ AND surface = 'discord_bot'
106
+ AND recorded_at >= date_trunc('month', now() AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`,
107
+ [route]
108
+ );
109
+ return Number(rows[0].usd) || 0;
110
+ }
111
+
82
112
  // --- the board mirror's own bookkeeping (R22 of goal 1000069) ----------------
83
113
  //
84
114
  // Which message announced which board item, so the close can be EDITED onto the
@@ -137,6 +167,8 @@ module.exports = {
137
167
  getDiscordLink,
138
168
  getDiscordLinks,
139
169
  countRecentDiscordBugFilings,
170
+ getBotPrincipalId,
171
+ sumSupportSpendThisMonth,
140
172
  recordApplause,
141
173
  recordBoardMessage,
142
174
  getBoardMessage,
@@ -471,6 +471,20 @@ async function start() {
471
471
  channelId: bugsChannelId, aggregator: bugAggregator, logLabel: 'bug handler',
472
472
  }));
473
473
 
474
+ // #support answers (task 1002534, ADR 0367): an @-mention in the pinned
475
+ // support channel gets a short answer from the public docs. Read-only, its
476
+ // own kill switch (DISCORD_SUPPORT_ENABLED) on top of this one, and a
477
+ // monthly spend cap. Everything lives in discord-support.js; it is the
478
+ // first feature that REPLIES through the gateway, so the bot needs Send
479
+ // Messages in that one channel.
480
+ const support = require('./discord-support').createLiveSupport({ client: c, db });
481
+ if (support.handler) {
482
+ c.on(Events.MessageCreate, support.handler);
483
+ log.info(`[discord-bot] answering @-mentions in #support (${support.channelId}), cap $${support.capUsd}/month.`);
484
+ } else {
485
+ log.info(`[discord-bot] #support answers are inert (${support.inertReason}).`);
486
+ }
487
+
474
488
  // Role-on-arrival (F7, ADR 0033 — the link-first onboarding gate's safety
475
489
  // net). The auto-join path (routes/auth.js) already sets the rank role at
476
490
  // add-time, so a builder who linked-then-joined is correct immediately. This