@bongos/core 1.19.126 → 1.19.127

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.126",
6
- "core_contract": "1.19.126",
7
- "source_commit": "b56cedc2da8ef6691bd7990c6e3219896694e540",
5
+ "core_version": "1.19.127",
6
+ "core_contract": "1.19.127",
7
+ "source_commit": "f4b67a76dc2ad911a7c9257ba4280de4ac7904c7",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-15T00:16:09.571Z",
9
+ "built_at": "2026-08-15T00:49:23.419Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 303,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1569,
20
- "tree_sha256": "5703791569f4c26556ad21f0bbcf605faf1b42cfd3631a1ce5f25a05269fd4b4",
20
+ "tree_sha256": "deedfbf01298656cc039a9d567722f3689778d62d8aacc97a230ac4490a39c7a",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -2367,7 +2367,7 @@
2367
2367
  {
2368
2368
  "path": "modules/builder-settings/builder-needs.js",
2369
2369
  "mode": "0000644",
2370
- "sha256": "0b40afc38f91ad053d5ecca380eb35d314a3f104fbe8096d51efeb3de131fc98"
2370
+ "sha256": "3cb7478d9e347147e331f0954a9178a1770918067a678efdb8caef96241dfb5a"
2371
2371
  },
2372
2372
  {
2373
2373
  "path": "modules/builder-settings/db.js",
@@ -4167,12 +4167,12 @@
4167
4167
  {
4168
4168
  "path": "package-lock.json",
4169
4169
  "mode": "0000644",
4170
- "sha256": "64edc372bb49cd5c30eb5a1d46f54361293de787d1717d2e116f52568ff93f2c"
4170
+ "sha256": "ecb64df7be6949adcb0d88b5f21ca3014724b598fa1a6d939a3a50d8f9696aa6"
4171
4171
  },
4172
4172
  {
4173
4173
  "path": "package.json",
4174
4174
  "mode": "0000644",
4175
- "sha256": "c652ffaf3901e4ccc8f64ff9340f7f4f6c418ab738fc52f2b71450083d2a4e97"
4175
+ "sha256": "5be9320e605a4b67ba2d63fa4ab831b99bda91a847c11338fd5e0ed6a65d0779"
4176
4176
  },
4177
4177
  {
4178
4178
  "path": "public-docs/index.html",
@@ -5527,12 +5527,12 @@
5527
5527
  {
5528
5528
  "path": "src/bongos/db-kernel.js",
5529
5529
  "mode": "0000644",
5530
- "sha256": "fa80ba2c0bb742b83d637280e65965f450bc0387063e65dd06f6bd9171283f23"
5530
+ "sha256": "f31dd847ed641bf49013c345b1f08f9e2794a875dad777de10daeda2a4ae1cf3"
5531
5531
  },
5532
5532
  {
5533
5533
  "path": "src/bongos/db.js",
5534
5534
  "mode": "0000644",
5535
- "sha256": "64b0efa7c0a251266a8dbc00d43373fb1516fa5c88ba737220c969ec97a2e98b"
5535
+ "sha256": "d3dd606664855a94788751d13ab7fc62ccdc4a8df48f331ec97a5a81a8a6fa96"
5536
5536
  },
5537
5537
  {
5538
5538
  "path": "src/bongos/github-repos.js",
@@ -5687,7 +5687,7 @@
5687
5687
  {
5688
5688
  "path": "src/bongos/routes/me.js",
5689
5689
  "mode": "0000644",
5690
- "sha256": "e6c9e19a4b644f35da665cc48df21085d9a187e3b694a73af01f3d1b83c620b8"
5690
+ "sha256": "83657d8a2f8e1d81a80324350ddd983baf5346da3f88ce1f41c2211275f34440"
5691
5691
  },
5692
5692
  {
5693
5693
  "path": "src/bongos/routes/modules.js",
@@ -5757,7 +5757,7 @@
5757
5757
  {
5758
5758
  "path": "src/module-api.js",
5759
5759
  "mode": "0000644",
5760
- "sha256": "c64972eb393e195b4f24de3459036423077430a5cf643fd2e3a8da525ab3af56"
5760
+ "sha256": "80e087f2181512cfdf51030c27aeb7aaa4235421c4e06c9d7c4a000d418b0d0a"
5761
5761
  },
5762
5762
  {
5763
5763
  "path": "src/module-loader/catalog.js",
@@ -6147,7 +6147,7 @@
6147
6147
  {
6148
6148
  "path": "tests/builder_needs.mjs",
6149
6149
  "mode": "0000644",
6150
- "sha256": "e3d8c5ac45d59f5d566547e2c406cf44ffa60f1b243afcb222b09eee68f5ee0d"
6150
+ "sha256": "49a8146cbb0ec5def5b48130ed61fdec6e9855f22fe5ad3a09b39ae2cb218ca8"
6151
6151
  },
6152
6152
  {
6153
6153
  "path": "tests/bump_version.mjs",
@@ -90,13 +90,68 @@ function artKeyNeed(ctx) {
90
90
  // pure (ctx) => need|null function here — every renderer picks it up for free.
91
91
  const NEEDS = [artKeyNeed];
92
92
 
93
+ // ---- founding grace (task 1002739 / direction §5.6, idea 1000701) -----------
94
+ //
95
+ // A freshly-provisioned instance used to greet its founding owner with "your
96
+ // free art coverage has ended" — a demand as the payoff moment of the whole
97
+ // create flow. While the founding owner's first days are running, the roster
98
+ // leads with a welcome and every action_needed item is held at 'covered'
99
+ // (listed, honest, but not demanding). First-run is a time window, not a
100
+ // visit-counter: counting the first hall visit would end the grace on the very
101
+ // request that renders it.
102
+ //
103
+ // ctx.isFoundingOwner — is this builder the instance's first admitted builder?
104
+ // ctx.founderCreatedAt — that first builder's created_at (≈ the instance's
105
+ // birth on a newborn); the window anchors here.
106
+ // ctx.projectName — branding identity.productName, for the greeting.
107
+ // ctx.nowMs — injectable clock for tests; defaults to Date.now().
108
+ const FOUNDING_GRACE_DAYS = 7;
109
+ const FOUNDING_GRACE_MS = FOUNDING_GRACE_DAYS * 24 * 60 * 60 * 1000;
110
+
111
+ function foundingGraceActive(ctx = {}) {
112
+ if (!ctx.isFoundingOwner || !ctx.founderCreatedAt) return false;
113
+ const born = new Date(ctx.founderCreatedAt).getTime();
114
+ if (!Number.isFinite(born)) return false;
115
+ const now = typeof ctx.nowMs === 'number' ? ctx.nowMs : Date.now();
116
+ return now - born < FOUNDING_GRACE_MS;
117
+ }
118
+
119
+ function foundingWelcomeNeed(ctx) {
120
+ const name = ctx.projectName || 'your project';
121
+ return {
122
+ id: 'founding_welcome',
123
+ title: `Welcome to ${name}`,
124
+ what: 'Your project is standing, and this hall is where it gets built.',
125
+ learn_more_url: null,
126
+ state: 'covered',
127
+ severity: 'info',
128
+ message: `You made it — ${name} is up, and this hall is its workshop. ` +
129
+ 'Look around at your own pace; if the platform ever needs something from you, ' +
130
+ 'it will say so here once your first days are behind you.',
131
+ action: null,
132
+ };
133
+ }
134
+
93
135
  // Compute the live list for a builder. `ctx` carries everything the need
94
136
  // functions read (see each need's doc). Returns:
95
137
  // { items: Need[], action_needed_count, has_action_needed }
96
138
  // items is ordered: action_needed first (loudest), then covered, then satisfied,
97
- // so any renderer that takes "the first one" gets the most urgent.
139
+ // so any renderer that takes "the first one" gets the most urgent. During the
140
+ // founding grace nothing is action_needed, so the welcome (prepended before the
141
+ // stable sort) is what that renderer shows.
98
142
  function computeNeeds(ctx = {}) {
99
- const items = NEEDS.map((fn) => fn(ctx)).filter(Boolean);
143
+ const grace = foundingGraceActive(ctx);
144
+ let items = NEEDS.map((fn) => fn(ctx)).filter(Boolean);
145
+ if (grace) {
146
+ items = items.map((n) => n.state !== 'action_needed' ? n : {
147
+ ...n,
148
+ state: 'covered',
149
+ severity: 'info',
150
+ message: 'Nothing needed from you today — we’ll bring this up once your first days are behind you.',
151
+ action: null,
152
+ });
153
+ items.unshift(foundingWelcomeNeed(ctx));
154
+ }
100
155
  const order = { action_needed: 0, covered: 1, satisfied: 2 };
101
156
  items.sort((a, b) => (order[a.state] ?? 9) - (order[b.state] ?? 9));
102
157
  const actionNeeded = items.filter((n) => n.state === 'action_needed');
@@ -111,5 +166,7 @@ module.exports = {
111
166
  computeNeeds,
112
167
  // exported for tests / direct reuse
113
168
  artKeyNeed,
169
+ foundingGraceActive,
170
+ FOUNDING_GRACE_DAYS,
114
171
  SETTINGS_PATH,
115
172
  };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.126",
3
+ "version": "1.19.127",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.126",
9
+ "version": "1.19.127",
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.126",
3
+ "version": "1.19.127",
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",
@@ -62,6 +62,20 @@ async function getBuilderById(id) {
62
62
  return rows[0] ?? null;
63
63
  }
64
64
 
65
+ // getFoundingBuilder — the instance's first admitted HUMAN builder (task
66
+ // 1002739): the founding owner a newborn instance greets, and the anchor of
67
+ // the builder-needs founding-grace window. system_role IS NULL is load-bearing:
68
+ // core migrations 065/067 seed the BFG + Discord-bot system principals into
69
+ // builders on EVERY instance before anyone can sign in, so the lowest ids on a
70
+ // newborn DB are not the founder. First human row by id, regardless of status —
71
+ // the founder stays the founder even if later deactivated.
72
+ async function getFoundingBuilder() {
73
+ const { rows } = await pool.query(
74
+ `SELECT id, created_at FROM builders WHERE system_role IS NULL ORDER BY id ASC LIMIT 1`
75
+ );
76
+ return rows[0] ?? null;
77
+ }
78
+
65
79
  // listActiveBuilders — the minimal, any-builder-readable roster (task 1877): an
66
80
  // invite-picker needs a real name of every builder to invite, not just the
67
81
  // leaderboard's top-100-by-credits window. Deliberately narrow columns — no
@@ -337,6 +351,7 @@ module.exports = {
337
351
  withTx,
338
352
  sessionPermsSelect,
339
353
  getBuilderById,
354
+ getFoundingBuilder,
340
355
  listActiveBuilders,
341
356
  insertAuditLog,
342
357
  LIVE_RANK_LADDER,
package/src/bongos/db.js CHANGED
@@ -38,7 +38,7 @@ const seams = require('../module-seams');
38
38
  // published doorway resolves through. Re-exported below so every caller is
39
39
  // byte-identical; db-kernel depends only on ./pool, never ./db (one-way).
40
40
  const {
41
- withTx, getBuilderById, listActiveBuilders, insertAuditLog, LIVE_RANK_LADDER,
41
+ withTx, getBuilderById, getFoundingBuilder, listActiveBuilders, insertAuditLog, LIVE_RANK_LADDER,
42
42
  // Auth/identity/admission + the rank vocabulary carved to db-kernel (ADR 0102,
43
43
  // finishing ADR 0091 §1). Re-exported below so db.js's other importers + its own
44
44
  // internal uses (setBuilderRank's RANKS.indexOf, etc.) stay byte-identical.
@@ -978,6 +978,7 @@ module.exports = {
978
978
  upsertBuilder,
979
979
  seatFirstAdminIfUnclaimed,
980
980
  getBuilderById,
981
+ getFoundingBuilder,
981
982
  listActiveBuilders,
982
983
  linkAnthropicAccount,
983
984
  getBuilderByGithubId,
@@ -83,7 +83,7 @@ module.exports = function buildMeRouter() {
83
83
  const [
84
84
  builder, activeClaims, estVsActual, unlocked, lockedWithProgress, streakInfo,
85
85
  recentGrades, builderAvg, onboardingRow, shipCount, recentKarma, karma30d, discordLink, boxConnected, cliAuthed,
86
- behaviorPrefs, geminiKeyRow, needsRebaseTasks,
86
+ behaviorPrefs, geminiKeyRow, needsRebaseTasks, foundingBuilder,
87
87
  ] = await Promise.all([
88
88
  db.getBuilderById(req.builder.id),
89
89
  lc().getActiveClaimsForBuilder(req.builder.id),
@@ -144,6 +144,10 @@ module.exports = function buildMeRouter() {
144
144
  // without a follow-up call. Through the `lifecycle` port; [] on a
145
145
  // lifecycle-less instance (guarded — the port is resolveOptional).
146
146
  lc() ? lc().needsRebaseTasksForBuilder(req.builder.id) : [],
147
+ // task 1002739: the instance's first admitted builder — anchors the
148
+ // builder-needs founding-grace window (a newborn instance's founder gets a
149
+ // welcome, not a nag). Best-effort: null just means no grace.
150
+ db.getFoundingBuilder().catch(() => null),
147
151
  ]);
148
152
  // V3.R84 (#302), criterion C3: the builder's month-to-date spend vs their
149
153
  // monthly budget. Surfaced here so the CLI (session-start ping) and the hall
@@ -274,6 +278,12 @@ module.exports = function buildMeRouter() {
274
278
  rank: builder?.rank,
275
279
  ownKeySet: !!(geminiKeyRow && geminiKeyRow.enc),
276
280
  sharedKeyConfigured: artSharedKeyConfigured(),
281
+ // founding grace (task 1002739 / direction §5.6): a newborn instance's
282
+ // founder gets a welcome roster, not a coverage nag. The module owns the
283
+ // policy (window length, copy); we pass the raw facts.
284
+ isFoundingOwner: !!(foundingBuilder && String(foundingBuilder.id) === String(req.builder.id)),
285
+ founderCreatedAt: foundingBuilder ? foundingBuilder.created_at : null,
286
+ projectName: (() => { try { return branding().identity?.productName || null; } catch { return null; } })(),
277
287
  }) : [],
278
288
  };
279
289
  // Module→core contribution seam (BV1.R42, ADR 0083). Let each enabled module
package/src/module-api.js CHANGED
@@ -679,8 +679,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
679
679
  // landed since 1.19.124 with no explicit bump. run 31849445746. (task 1002620)
680
680
  // 1.19.126 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
681
681
  // landed since 1.19.125 with no explicit bump. run 31853055269. (task 1002620)
682
+ // 1.19.127 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
683
+ // landed since 1.19.126 with no explicit bump. run 31854730552. (task 1002620)
682
684
  // ---------------------------------------------------------------------------
683
- const CORE_VERSION = '1.19.126'; // CI auto-patch carrier (ADR 0161); see changelog above
685
+ const CORE_VERSION = '1.19.127'; // CI auto-patch carrier (ADR 0161); see changelog above
684
686
 
685
687
  // A namespaced logger so a module's log lines are attributable + consistent.
686
688
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -95,5 +95,119 @@ t('missing rank fails safe — not action_needed without an explicit rank', () =
95
95
  assert.ok(['covered', 'action_needed', 'satisfied'].includes(n.state));
96
96
  });
97
97
 
98
+ console.log('founding grace (task 1002739, direction §5.6):');
99
+
100
+ const DAY = 24 * 60 * 60 * 1000;
101
+ const bornNow = new Date().toISOString();
102
+
103
+ t('newborn founder: welcome item first, greeting by project name', () => {
104
+ const r = needs.computeNeeds({
105
+ rank: 'archon', ownKeySet: false,
106
+ isFoundingOwner: true, founderCreatedAt: bornNow, projectName: 'Mercury',
107
+ });
108
+ assert.equal(r.items[0].id, 'founding_welcome');
109
+ assert.match(r.items[0].title, /Mercury/);
110
+ assert.equal(r.items[0].state, 'covered');
111
+ assert.equal(r.items[0].action, null);
112
+ });
113
+
114
+ t('newborn founder: zero action_needed — every nag is suppressed', () => {
115
+ const r = needs.computeNeeds({
116
+ rank: 'archon', ownKeySet: false, isFoundingOwner: true, founderCreatedAt: bornNow,
117
+ });
118
+ assert.equal(r.has_action_needed, false);
119
+ assert.equal(r.action_needed_count, 0);
120
+ for (const n of r.items) assert.notEqual(n.state, 'action_needed');
121
+ });
122
+
123
+ t('a suppressed nag keeps its identity but loses the demand', () => {
124
+ const r = needs.computeNeeds({
125
+ rank: 'archon', ownKeySet: false, isFoundingOwner: true, founderCreatedAt: bornNow,
126
+ });
127
+ const artItem = r.items.find((n) => n.id === 'art_key');
128
+ assert.ok(artItem, 'the art_key item must still be listed, not hidden');
129
+ assert.equal(artItem.state, 'covered');
130
+ assert.equal(artItem.action, null);
131
+ assert.doesNotMatch(artItem.message, /coverage has ended/i);
132
+ });
133
+
134
+ t('the grace window is seven days — the policy number is pinned, not self-referential', () => {
135
+ // A fixed injected clock, and 7 written as a literal on both sides: mutating
136
+ // FOUNDING_GRACE_DAYS (a "tune", a bad merge) must fail here, not slide.
137
+ assert.equal(needs.FOUNDING_GRACE_DAYS, 7);
138
+ const born = Date.parse('2026-01-01T00:00:00Z');
139
+ const ctx = { rank: 'archon', ownKeySet: false, isFoundingOwner: true, founderCreatedAt: '2026-01-01T00:00:00Z' };
140
+ const inWindow = needs.computeNeeds({ ...ctx, nowMs: born + 7 * DAY - 60_000 });
141
+ assert.equal(inWindow.has_action_needed, false);
142
+ assert.equal(inWindow.items[0].id, 'founding_welcome');
143
+ const expired = needs.computeNeeds({ ...ctx, nowMs: born + 7 * DAY + 60_000 });
144
+ assert.equal(expired.has_action_needed, true);
145
+ assert.ok(!expired.items.find((n) => n.id === 'founding_welcome'));
146
+ });
147
+
148
+ t('grace expires: past FOUNDING_GRACE_DAYS the founder is nagged like anyone (real clock)', () => {
149
+ const old = new Date(Date.now() - (needs.FOUNDING_GRACE_DAYS * DAY + 60_000)).toISOString();
150
+ const r = needs.computeNeeds({
151
+ rank: 'archon', ownKeySet: false, isFoundingOwner: true, founderCreatedAt: old,
152
+ });
153
+ assert.equal(r.has_action_needed, true);
154
+ assert.ok(!r.items.find((n) => n.id === 'founding_welcome'));
155
+ });
156
+
157
+ t('a non-founder on a newborn instance is not suppressed', () => {
158
+ const r = needs.computeNeeds({
159
+ rank: 'thetes', ownKeySet: false, isFoundingOwner: false, founderCreatedAt: bornNow,
160
+ });
161
+ assert.equal(r.has_action_needed, true);
162
+ assert.ok(!r.items.find((n) => n.id === 'founding_welcome'));
163
+ });
164
+
165
+ t('satisfied stays satisfied during grace — no fake downgrades', () => {
166
+ const r = needs.computeNeeds({
167
+ rank: 'archon', ownKeySet: true, isFoundingOwner: true, founderCreatedAt: bornNow,
168
+ });
169
+ assert.equal(r.items.find((n) => n.id === 'art_key').state, 'satisfied');
170
+ });
171
+
172
+ t('no founder facts at all → no grace, no welcome (vanilla ctx unchanged)', () => {
173
+ const r = needs.computeNeeds({ rank: 'thetes', ownKeySet: false });
174
+ assert.equal(r.has_action_needed, true);
175
+ assert.ok(!r.items.find((n) => n.id === 'founding_welcome'));
176
+ });
177
+
178
+ console.log('the producing side (DB-free source pins — the policy is only as real as its facts):');
179
+
180
+ // The seven policy cases above hand computeNeeds its ctx. These pin the two
181
+ // places that PRODUCE that ctx, because both were wrong-or-unverified once:
182
+ // the founder query returned the migration-seeded BFG system principal (ids 1-2
183
+ // on every newborn DB — review panel, 2026-08-15), and a key rename in me.js
184
+ // would silently disable the grace while every policy case stayed green. A
185
+ // DB-backed newborn-instance test can't run in the unit gate, so the source is
186
+ // the checkable artifact.
187
+ import { readFileSync } from 'node:fs';
188
+ import { fileURLToPath } from 'node:url';
189
+ import path from 'node:path';
190
+ const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
191
+
192
+ t('getFoundingBuilder skips the seeded system principals (system_role IS NULL)', () => {
193
+ const src = readFileSync(path.join(ROOT, 'src/bongos/db-kernel.js'), 'utf8');
194
+ const fn = src.slice(src.indexOf('function getFoundingBuilder'));
195
+ const sql = fn.slice(0, fn.indexOf('return'));
196
+ assert.match(sql, /FROM builders/);
197
+ assert.match(sql, /system_role IS NULL/,
198
+ 'the founder query must exclude system principals — migrations 065/067 seed them at the lowest ids on every instance');
199
+ assert.match(sql, /ORDER BY id ASC/);
200
+ });
201
+
202
+ t('the /me aggregator feeds computeNeeds the founder facts under their contract names', () => {
203
+ const src = readFileSync(path.join(ROOT, 'src/bongos/routes/me.js'), 'utf8');
204
+ const call = src.slice(src.indexOf('computeNeeds({'), src.indexOf('}) : []'));
205
+ for (const key of ['isFoundingOwner', 'founderCreatedAt', 'projectName']) {
206
+ assert.match(call, new RegExp(`${key}\\s*:`),
207
+ `me.js must pass ${key} into computeNeeds — a rename silently disables the founding grace`);
208
+ }
209
+ assert.match(src, /getFoundingBuilder\(\)/, 'me.js must resolve the founder row');
210
+ });
211
+
98
212
  console.log(`\n${passed} passed, ${failed} failed`);
99
213
  process.exit(failed ? 1 : 0);