@bongos/core 1.19.381 → 1.19.382

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.
@@ -20,6 +20,7 @@ const db = require('./db');
20
20
  const classifier = require('./task-classifier');
21
21
  const helpRequests = require('./help-requests');
22
22
  const taskRecommendations = require('./task-recommendations');
23
+ const activity = require('./activity');
23
24
 
24
25
  module.exports = {
25
26
  // --- retired doorway leaks (ADR 0093 §3) ---------------------------------
@@ -58,6 +59,12 @@ module.exports = {
58
59
  recentGradesForBuilder: (...a) => db.recentGradesForBuilder(...a),
59
60
  countShippedTasksForBuilder: (...a) => db.countShippedTasksForBuilder(...a),
60
61
  recentShippedTasksByBuilder: (...a) => db.recentShippedTasksByBuilder(...a),
62
+ // builders.js (the profile's per-craft activity log, task 1003281): the paged
63
+ // "what did I do as an engineer / an artist" reads. Separate from the two
64
+ // above because those answer "how much, in total" and these answer "what,
65
+ // by craft" — the axis criterion legibility-activity-by-role asks for.
66
+ activityTasksByBuilder: (...a) => activity.activityTasksByBuilder(...a),
67
+ activityGoalsByBuilder: (...a) => activity.activityGoalsByBuilder(...a),
61
68
  // ADR 0120 part 5: the confirmed tasks a builder owes a rebase on — the same
62
69
  // read the part-3 claim gate uses, surfaced to me.js so the CLI + hall can
63
70
  // show the owed-rebase warning that gates new claims (reward lands on ship).
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.381",
3
+ "version": "1.19.382",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.381",
9
+ "version": "1.19.382",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.381",
3
+ "version": "1.19.382",
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",
@@ -82,6 +82,11 @@ const FIXTURE_FALLBACKS = [
82
82
  [/^analytics\/builder\/\d+$/, 'analytics__builder'],
83
83
  [/^builders\/\d+\/active-claims$/, 'builders__active-claims'],
84
84
  [/^builders\/\d+\/profile$/, 'builders__profile'],
85
+ // the profile's per-craft activity log (task 1003281). One fixture per SECTION
86
+ // rather than per id, because the section is what changes the row shape; the
87
+ // craft and the page window are query string, which fixture matching ignores.
88
+ [/^builders\/\d+\/activity$/, (q) => (q.section === 'goals' ? 'builders__activity-goals' : 'builders__activity-tasks')],
89
+ [/^inbox\/by-builder\/\d+$/, 'inbox__by-builder'],
85
90
  ];
86
91
 
87
92
  function loadFixture(name) {
@@ -102,12 +107,17 @@ function emptied(v) {
102
107
  return v;
103
108
  }
104
109
 
105
- function fixtureFor(apiPath) {
110
+ // A fallback name may be a plain string or a function of the query — the one
111
+ // case that needs it (task 1003281) is the profile activity log, where the ROW
112
+ // SHAPE is chosen by ?section=, not by the path, so one fixture per path would
113
+ // render goal rows into the tasks ledger.
114
+ function fixtureFor(apiPath, query = {}) {
106
115
  const exact = loadFixture(apiPath.replace(/\//g, '__'));
107
116
  if (exact !== undefined) return exact;
108
117
  for (const [re, name] of FIXTURE_FALLBACKS) {
109
118
  if (re.test(apiPath)) {
110
- const hit = loadFixture(name);
119
+ const resolved = typeof name === 'function' ? name(query) : name;
120
+ const hit = resolved && loadFixture(resolved);
111
121
  if (hit !== undefined) return hit;
112
122
  }
113
123
  }
@@ -141,7 +151,7 @@ app.use((req, res, next) => {
141
151
  if (req.method !== 'GET') {
142
152
  return res.status(501).json({ error: { code: 'preview_readonly', message: 'the preview harness serves fixtures; writes are not wired' } });
143
153
  }
144
- const fix = fixtureFor(apiPath);
154
+ const fix = fixtureFor(apiPath, req.query || {});
145
155
  if (fix === undefined) {
146
156
  console.log(` no fixture: ${apiPath} (add scripts/hall-preview/fixtures/${apiPath.replace(/\//g, '__')}.json)`);
147
157
  return res.status(404).json({ error: { code: 'no_fixture', message: apiPath } });
@@ -545,6 +545,73 @@ module.exports = function buildBuildersRouter() {
545
545
  }
546
546
  });
547
547
 
548
+ // GET /builders/:id/activity — the profile's PER-CRAFT activity log (task
549
+ // 1003281, goal 1000072, criterion legibility-activity-by-role).
550
+ //
551
+ // WHAT IT ADDS OVER /profile. The profile answers "how much has this person
552
+ // shipped" — one count and the last five works, craft-blind. A person's artist
553
+ // track record is a different question from their engineer one, so this route
554
+ // serves ONE section (`tasks` or `goals`) for ONE craft, paged. It EXTENDS the
555
+ // profile surface rather than forking it: the page is the same page, the
556
+ // identity band and the achievement shelf are unchanged, and this fills the
557
+ // ledger below them. The third section of that log — the IDEA TRAIL — is
558
+ // served by the ideas module's GET /inbox/by-builder/:id, because an idea is
559
+ // its domain's row, not a lifecycle one.
560
+ //
561
+ // ONE SECTION PER CALL, not all three in one payload: each ledger pages
562
+ // independently, and a single response carrying three page windows would need
563
+ // three limit/offset pairs on one query string to say anything at all.
564
+ //
565
+ // rank: any-builder, NOT owner-only — a track record is a thing other people
566
+ // read; that is what makes the profile a portfolio rather than a dashboard, and
567
+ // it is the same posture /profile, /builders/roster and the leaderboard already
568
+ // take toward the same rows. The per-viewer filtering that DOES matter is the
569
+ // private-goal wall below, and it is applied to the DATA, not to the caller.
570
+ //
571
+ // But, unlike /profile directly above, this read IS gated to a sign-in.
572
+ // The goals half is viewer-dependent: a private goal's title is exactly what
573
+ // ADR 0112 says a non-member must not learn, so the read needs to know who is
574
+ // asking. Serving it publicly would mean either leaking private goals or
575
+ // silently omitting the viewer's own — both worse than a sign-in. The tasks
576
+ // half is the same already-public shipped-work data /profile returns.
577
+ router.get('/builders/:id/activity', auth.requireBuilder, async (req, res) => {
578
+ const targetId = parseId(req, res, { code: 'bad_builder_id', positiveInt: true });
579
+ if (targetId === null) return;
580
+ const section = req.query.section === 'goals' ? 'goals' : 'tasks';
581
+ // An unknown craft is normalized to "no filter" inside the module read
582
+ // rather than 400ing here — a stale bookmark should show the whole record,
583
+ // not an error card in the middle of a profile.
584
+ const role = typeof req.query.role === 'string' && req.query.role ? req.query.role : null;
585
+ const limit = Number(req.query.limit);
586
+ const offset = Number(req.query.offset);
587
+ try {
588
+ const b = await db.getBuilderById(targetId);
589
+ if (!b) return res.fail('builder_not_found', 404);
590
+ const viewer = req.builder || {};
591
+ const out = section === 'goals'
592
+ ? await lc().activityGoalsByBuilder(targetId, {
593
+ role,
594
+ viewerId: viewer.id ?? null,
595
+ viewerIsArchon: viewer.rank === 'archon',
596
+ limit,
597
+ offset,
598
+ })
599
+ : await lc().activityTasksByBuilder(targetId, { role, limit, offset });
600
+ // Viewer-dependent (the goals half) and cheap to recompute — never cached.
601
+ res.set('Cache-Control', 'no-store');
602
+ res.json({
603
+ builder_id: targetId,
604
+ section,
605
+ role: role || null,
606
+ rows: out.rows,
607
+ total: out.total,
608
+ });
609
+ } catch (err) {
610
+ log.error('[gds] GET /builders/:id/activity', err);
611
+ res.fail('activity_read_failed', { status: 500, message: 'internal error' });
612
+ }
613
+ });
614
+
548
615
  // GET /builders/me/pending-rank-change — V3.R28 / #249 ceremony signal #3.
549
616
  //
550
617
  // Returns the calling builder's most recent rank-change credit_log row, or
package/src/module-api.js CHANGED
@@ -1202,8 +1202,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1202
1202
  // landed since 1.19.379 with no explicit bump. run 33085516721. (task 1002620)
1203
1203
  // 1.19.381 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1204
1204
  // landed since 1.19.380 with no explicit bump. run 33088290961. (task 1002620)
1205
+ // 1.19.382 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1206
+ // landed since 1.19.381 with no explicit bump. run 33094545251. (task 1002620)
1205
1207
  // ---------------------------------------------------------------------------
1206
- const CORE_VERSION = '1.19.381'; // CI auto-patch carrier (ADR 0161); see changelog above
1208
+ const CORE_VERSION = '1.19.382'; // CI auto-patch carrier (ADR 0161); see changelog above
1207
1209
 
1208
1210
  // A namespaced logger so a module's log lines are attributable + consistent.
1209
1211
  // Usage: const log = api.logger('dev-box'); log.info('mounted');