@bongos/core 1.19.115 → 1.19.116

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,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.115",
6
- "core_contract": "1.19.115",
7
- "source_commit": "f98d169e99486bbb55d50e2fb3b18d2d0e035400",
5
+ "core_version": "1.19.116",
6
+ "core_contract": "1.19.116",
7
+ "source_commit": "be42420efe0538b351e5eed4a355a47e28b430f5",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-14T17:50:49.780Z",
9
+ "built_at": "2026-08-14T18:14:13.252Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 302,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1235,
14
+ "functional_verbatim": 1236,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1558,
20
- "tree_sha256": "85dc1b669e706f57dd7ae065edc3c04eba737c5875959566b2487e6b01053e59",
19
+ "file_count": 1559,
20
+ "tree_sha256": "8ac36ba3efd387f4c72fecb2fde8b6d68ae8c016c5e8336c2f78a24abb5791e1",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -4152,12 +4152,12 @@
4152
4152
  {
4153
4153
  "path": "package-lock.json",
4154
4154
  "mode": "0000644",
4155
- "sha256": "d7ad6805c0d040d4d14e0f95b7be3fc175b3e89068e8cb17b08be555827fe029"
4155
+ "sha256": "87bcf76333165f80466bc093e13d0ad6313d63db0e8518b083adf18cfc4c373d"
4156
4156
  },
4157
4157
  {
4158
4158
  "path": "package.json",
4159
4159
  "mode": "0000644",
4160
- "sha256": "feca9d093739c6fc3c7067bc4fbe725ff7e8cffd9420ddb6d2be632542b03667"
4160
+ "sha256": "f7a325a5a15a160f72580f8af598cf302a68ec9fc04a03085dcdfa341c52dc89"
4161
4161
  },
4162
4162
  {
4163
4163
  "path": "public-docs/index.html",
@@ -5204,6 +5204,11 @@
5204
5204
  "mode": "0000644",
5205
5205
  "sha256": "5de4203d6a764c8aec374c7802f69f659816d2c0d75bbe70144f7dfb56df2e39"
5206
5206
  },
5207
+ {
5208
+ "path": "scripts/gds/seed-bv1-profile-page-tasks.js",
5209
+ "mode": "0000644",
5210
+ "sha256": "68f6edfcea2eaaf1262dc125d538f2b3806c58864839d6e34c3135235f29ed4b"
5211
+ },
5207
5212
  {
5208
5213
  "path": "scripts/gds/seed-bv1-profiles-tasks.js",
5209
5214
  "mode": "0000644",
@@ -5727,7 +5732,7 @@
5727
5732
  {
5728
5733
  "path": "src/module-api.js",
5729
5734
  "mode": "0000644",
5730
- "sha256": "e80df1f75bd2a569a72d29eaa00146960390d86ef9a6fd66535d89aac6e6bd39"
5735
+ "sha256": "298ca0ddbe3bcae48f5c360ca66aea09a798f317816cbbb9180142b12a7fb6ae"
5731
5736
  },
5732
5737
  {
5733
5738
  "path": "src/module-loader/catalog.js",
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.115",
3
+ "version": "1.19.116",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.115",
9
+ "version": "1.19.116",
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.115",
3
+ "version": "1.19.116",
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",
@@ -0,0 +1,161 @@
1
+ // Seed the delta tasks the profile-page spike (task 1002783) surfaced, under
2
+ // goal 1000045 "Public builder profiles" (BONGOS-V1, internal).
3
+ //
4
+ // Source of truth: docs/specs/bongos-v1-builder-profile-page.md (status: final).
5
+ // The Phase-1 spine (R01-R06) was already seeded by seed-bv1-profiles-tasks.js;
6
+ // this file adds ONLY what that spec found on top of it, so the two seeders are
7
+ // disjoint and both stay re-runnable.
8
+ //
9
+ // Idempotent on (source, source_ref) — re-running resolves to the existing rows
10
+ // and re-asserts the dependency edges.
11
+ //
12
+ // Run: node scripts/gds/seed-bv1-profile-page-tasks.js
13
+
14
+ const { apiCall } = require('./cli-lib');
15
+
16
+ const VER = 'BONGOS-V1';
17
+ const GOAL = 1000045;
18
+ const SRC = 'planning-session';
19
+ const SPEC = 'docs/specs/bongos-v1-builder-profile-page.md';
20
+
21
+ // Tasks that already exist and that these depend on — referenced by id, since
22
+ // they were seeded by the sibling seeder (or by the wave task 1002781).
23
+ const R01 = 1002277; // spike · design profile↔rollup join + consent model
24
+ const R02 = 1002278; // feature · profile API returns cross-project record
25
+ const R03 = 1002279; // feature · hide flag
26
+ const R05 = 1002281; // feature · shareable-by-login profile URL
27
+ const SETTINGS_SPIKE = 1002784; // spike · account settings page — inventory + placement
28
+ const PRIVACY_SPIKE = 1002785; // spike · account privacy model + invite inbox
29
+
30
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
31
+
32
+ // Throttled + 429-aware wrapper. The seeder makes ~20 writes in a burst, which
33
+ // is exactly the shape the per-IP limiter is there to catch.
34
+ async function api(method, path, body, _tries = 0) {
35
+ await sleep(400);
36
+ const r = await apiCall(method, `/api/gds${path}`, body);
37
+ if (r.status === 429 && _tries < 8) {
38
+ const wait = (r.data?.error?.details?.retry_after_seconds || 30) + 1;
39
+ console.log(` …rate-limited, waiting ${wait}s`);
40
+ await sleep(wait * 1000);
41
+ return api(method, path, body, _tries + 1);
42
+ }
43
+ return r;
44
+ }
45
+
46
+ // deps entries are either a local ref ('P01') or a numeric id of an existing task.
47
+ const TASKS = [
48
+ {
49
+ ref: 'P01', kind: 'feature', disc: 'engineer', prio: 5, est: 180, crit: ['C1'], deps: [R01],
50
+ title: 'P01 · Builder handle on platform accounts (seed, dedupe, reserved list, rename)',
51
+ desc: `Add a \`handle\` column to platform_identity_accounts: seeded from github_login at creation, deduped with a numeric suffix on collision, validated against a small reserved list (me, new, edit, settings, admin, api, u), builder-editable at a low rate limit, previous handle retained for a 301 window. github_id stays the join key everywhere — the handle is presentation only. WHY: github_login is mutable, so hanging the canonical profile URL on it means every GitHub rename silently 404s that builder's shared links. Namespaced module migration (ADR 0083). Spec: ${SPEC} (Decision 3).`,
52
+ },
53
+ {
54
+ ref: 'P02', kind: 'feature', disc: 'engineer', prio: 5, est: 180, crit: ['C1'], deps: [R01],
55
+ title: 'P02 · Profile fields: bio + typed links on the platform account',
56
+ desc: `Add \`bio\` (plain text, ~280 chars, NO markdown and no auto-linking) and \`links\` (a TYPED, capped set — website, X, LinkedIn, Discord; max 4) to platform_identity_accounts, plus the authenticated write. Server-side validation must enforce https-only AND reject non-http(s) schemes and userinfo/host tricks — do not rely on the client. Render with rel="nofollow ugc noopener". WHY typed-and-capped rather than a free-form link list: a public, indexable page with user-controlled links is an SEO-spam magnet, and this removes the incentive without a moderation queue. Spec: ${SPEC} (Decision 4).`,
57
+ },
58
+ {
59
+ ref: 'P03', kind: 'decision', disc: 'engineer', prio: 5, est: 60, crit: ['C1'], deps: [], ready: true,
60
+ title: 'P03 · Owner decision: provisional vs active profile state for check-in-created accounts',
61
+ desc: `Goal 1000045 justifies "public by default" as tied to having a platform account, "which already carries platform T&C consent". That premise is not currently true for every account: POST /sso/membership/check-in and POST /sso/activity/rollup cause the hub to upsert an account for a builder who has never visited the hub and never saw a platform T&C. Under auto-create (see task 1002783) those people would get a public, indexable, cross-project profile from a sign-in they performed on someone else's instance. RECOMMENDATION: a profile_state of 'provisional' (created by check-in — still counted in aggregates, but not publicly listed, not at the handle URL, not in the sitemap, not in hub search) vs 'active' (has signed in at the hub at least once). One column, one branch. This is an owner product/consent call, and it changes the shape of P09. Spec: ${SPEC}.`,
62
+ },
63
+ {
64
+ ref: 'P04', kind: 'feature', disc: 'ui', prio: 4, est: 180, crit: ['C1'], deps: ['P01', 'P02', SETTINGS_SPIKE],
65
+ title: 'P04 · Settings → Profile rail group (the single profile-edit surface)',
66
+ desc: `One edit surface for the profile, as a new rail group in the hall Settings page (the RAIL_GROUPS pattern in modules/hall-ui/public/settings.js) — not a separate edit page. Holds handle, bio, links alongside the display-name and disciplines controls that already live there. Account-global fields must be edited where they are stored; final hub-vs-per-project placement is task ${SETTINGS_SPIKE}'s call, which is why this waits on it. Spec: ${SPEC} (Decision 4).`,
67
+ },
68
+ {
69
+ ref: 'P05', kind: 'verify', disc: 'engineer', prio: 5, est: 120, crit: ['C1'], deps: [R02],
70
+ title: 'P05 · Leak test for the hub profile read (mirror of tests/profile_route.mjs)',
71
+ desc: `The instance route GET /builders/:id/profile projects an explicit public column whitelist and tests/profile_route.mjs proves the private columns (anthropic_email, anthropic_user_id, monthly_budget_usd, system_role, box_blocked*, deactivated_reason) never appear anywhere in the serialized response. The new cross-project read needs its OWN mirror test over the account + rollup projection before it is public — same sentinel-string technique, same 404-on-unknown-id assertion. Never spread a raw row. Spec: ${SPEC} (Constraints §1).`,
72
+ },
73
+ {
74
+ ref: 'P06', kind: 'feature', disc: 'ui', prio: 3, est: 120, crit: ['C1'], deps: ['P02'],
75
+ title: 'P06 · "Complete your profile" needs-attention inbox line item (dismissible)',
76
+ desc: `When bio or preferred_disciplines are empty, nudge the builder — as a DISMISSIBLE line item in the needs-attention inbox (goal-inbox.js / #attention-scroll), never a new Home card. That is the hall placement rule (modules/hall-ui/CLAUDE.md → Placement): an action awaiting the builder is an inbox line. This is the replacement for the profile-setup wizard that task 1002783 rejected. Spec: ${SPEC} (Decision 2).`,
77
+ },
78
+ {
79
+ ref: 'P07', kind: 'feature', disc: 'engineer', prio: 3, est: 210, crit: ['C1'], deps: [R02],
80
+ title: 'P07 · Hub recent works via push — recent_ships[] on the activity rollup',
81
+ desc: `The hub has no tasks table for other projects, so "recent works" cannot render there today. The existing PULL pattern (modules/platform-identity/project-stats.js) fans out to each project's public API — right for a builder count on a catalog page, wrong per-profile-view, and it cannot serve a CLI-only project at all. Extend the POST /sso/activity/rollup payload (which already carries credits/ships/karma/rank/disciplines per project) with a capped recent_ships[] — 3 per project: title, id, shipped_at, and a link into that instance — plus the instance-side reporter in core auth.js. Reuses a table and a reporter that already exist; costs no fan-out read; stays descriptive-analytics-only. Spec: ${SPEC} (page spec, section 4).`,
82
+ },
83
+ {
84
+ ref: 'P08', kind: 'feature', disc: 'ui', prio: 4, est: 150, crit: ['C1'], deps: ['P01', R05],
85
+ title: 'P08 · Canonical profile URL wiring (instance rel=canonical → hub; numeric id redirects)',
86
+ desc: `One canonical public profile, two render paths: the hub's server-rendered handle URL is canonical, and the hall's own profile page stays the LOCAL view and emits rel=canonical to it when the builder has a platform account. The numeric ?id= form never dies — it is stable and rename-proof, so keep it as a permanent fallback that redirects to the canonical handle URL. Coordinate with goal 1000048 R14 (public builder-profile SEO page) so only ONE page gets built. Spec: ${SPEC} (Decision 1).`,
87
+ },
88
+ {
89
+ ref: 'P09', kind: 'feature', disc: 'engineer', prio: 3, est: 150, crit: ['C1'], deps: ['P01', PRIVACY_SPIKE],
90
+ title: 'P09 · noindex until first ship + sitemap consent gate for builder profiles',
91
+ desc: `A public, indexable profile that anyone can mint is a backlink-farming surface. Gate indexing on the builder having at least one shipped work, and gate sitemap inclusion on consent (private accounts and — if the owner accepts P03 — provisional accounts are excluded). Coordinates with goal 1000048 R15 (builder profiles in sitemap, consent-gated). Shape depends on the P03 decision. Spec: ${SPEC} (Visibility rules).`,
92
+ },
93
+ {
94
+ ref: 'P10', kind: 'verify', disc: 'engineer', prio: 3, est: 90, crit: ['C2'], deps: [PRIVACY_SPIKE, R03],
95
+ title: 'P10 · PROOF: a private account\'s profile 404s to a stranger and renders to its owner',
96
+ desc: `Verify the profile page honours the privacy model: a private account's handle URL returns 404 — NOT 403, because a 403 confirms the handle exists — to everyone except the account itself, which sees its own profile with a "private" banner. Also assert the stats-hidden case renders no partial numbers (a count beside a hidden total is a leak by subtraction). The authoritative model is task ${PRIVACY_SPIKE}'s; if it lands a different rule, this proof follows it and the spec is the thing that is wrong. Spec: ${SPEC} (Visibility rules).`,
97
+ },
98
+ ];
99
+
100
+ (async () => {
101
+ const idByRef = {};
102
+ let created = 0;
103
+ let existing = 0;
104
+ console.log(`Seeding ${TASKS.length} tasks under goal ${GOAL} (${VER})…`);
105
+ for (const t of TASKS) {
106
+ const r = await api('POST', '/tasks', {
107
+ version_id: VER,
108
+ goal_id: GOAL,
109
+ title: t.title,
110
+ description: t.desc,
111
+ kind: t.kind,
112
+ discipline: t.disc,
113
+ priority: t.prio,
114
+ est_minutes: t.est,
115
+ criterion_ids: t.crit,
116
+ source: SRC,
117
+ source_ref: `bv1-profile-page-${t.ref}`,
118
+ });
119
+ if (!r.ok) {
120
+ console.log(` ${t.ref}: FAILED ${r.status} ${JSON.stringify(r.data)}`);
121
+ continue;
122
+ }
123
+ const task = r.data.task || r.data;
124
+ idByRef[t.ref] = task.id;
125
+ if (r.data.idempotent || r.data.existing) existing++; else created++;
126
+ console.log(` ${t.ref} → task ${task.id} (${t.kind}/${t.disc}, P${t.prio}, [${t.crit.join(',')}])`);
127
+ }
128
+
129
+ console.log('Wiring dependencies…');
130
+ let edges = 0;
131
+ for (const t of TASKS) {
132
+ if (!t.deps.length) continue;
133
+ const id = idByRef[t.ref];
134
+ // A dep is either a local ref (resolved from this run) or a literal task id.
135
+ const depIds = t.deps.map((d) => (typeof d === 'number' ? d : idByRef[d])).filter(Boolean);
136
+ if (!id || depIds.length !== t.deps.length) {
137
+ console.log(` ${t.ref}: dep resolve gap, skipping`);
138
+ continue;
139
+ }
140
+ // PUT replaces the full set, so a re-run converges instead of erroring on
141
+ // an edge that already exists.
142
+ const r = await api('PUT', `/tasks/${id}/dependencies`, { depends_on_task_ids: depIds });
143
+ if (!r.ok) {
144
+ console.log(` ${t.ref}: dep FAILED ${r.status} ${JSON.stringify(r.data)}`);
145
+ continue;
146
+ }
147
+ edges += depIds.length;
148
+ console.log(` ${t.ref} depends on ${t.deps.join(',')}`);
149
+ }
150
+
151
+ console.log('Promoting zero-dep roots to ready…');
152
+ for (const t of TASKS.filter((x) => x.ready)) {
153
+ const id = idByRef[t.ref];
154
+ if (!id) continue;
155
+ const r = await api('POST', `/tasks/${id}/promote`, {});
156
+ console.log(` ${t.ref} (task ${id}): ${r.ok ? 'ready' : 'promote note ' + JSON.stringify(r.data).slice(0, 120)}`);
157
+ }
158
+
159
+ console.log(`\nDONE. created=${created} existing=${existing} depEdges=${edges}.`);
160
+ console.log('ref→id:', JSON.stringify(idByRef));
161
+ })();
package/src/module-api.js CHANGED
@@ -657,8 +657,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
657
657
  // landed since 1.19.113 with no explicit bump. run 31816599678. (task 1002620)
658
658
  // 1.19.115 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
659
659
  // landed since 1.19.114 with no explicit bump. run 31825824596. (task 1002620)
660
+ // 1.19.116 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
661
+ // landed since 1.19.115 with no explicit bump. run 31827642395. (task 1002620)
660
662
  // ---------------------------------------------------------------------------
661
- const CORE_VERSION = '1.19.115'; // CI auto-patch carrier (ADR 0161); see changelog above
663
+ const CORE_VERSION = '1.19.116'; // CI auto-patch carrier (ADR 0161); see changelog above
662
664
 
663
665
  // A namespaced logger so a module's log lines are attributable + consistent.
664
666
  // Usage: const log = api.logger('dev-box'); log.info('mounted');