@bongos/core 1.19.115 → 1.19.117
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 +39 -24
- package/clients/bongos-client/index.d.ts +2 -2
- package/docs/api/openapi.json +6 -0
- package/docs/api-reference.md +2 -2
- package/modules/platform-identity/migrations/platform_identity_006_project_type.sql +23 -0
- package/modules/platform-identity/platform-identity.js +20 -10
- package/modules/platform-identity/routes/projects.js +2 -0
- package/modules/platform-identity/tests/platform-identity.mjs +14 -0
- package/modules/provisioning/migrations/provisioning_007_project_type.sql +24 -0
- package/modules/provisioning/provisioning.js +17 -5
- package/modules/provisioning/routes/provisioning.js +14 -0
- package/modules/provisioning/tests/provisioning.mjs +7 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +5 -1
- package/public-landing/projects.html +150 -34
- package/scripts/gds/provision.js +3 -1
- package/scripts/gds/seed-bv1-profile-page-tasks.js +161 -0
- package/src/module-api.js +5 -1
- package/tests/cosmos_token_contrast.mjs +7 -0
- package/tests/projects_hub.mjs +42 -11
|
@@ -58,7 +58,7 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
|
|
|
58
58
|
over one, so both modes and a re-brand carry it for free (C1/C2/C4).
|
|
59
59
|
Naming: the wizard's field wrapper is .wizField — cosmos owns .field (the
|
|
60
60
|
orb map) — and its done checklist is .humanSteps — cosmos owns .steps
|
|
61
|
-
(the
|
|
61
|
+
(the five-beat rail). Its inputs are scoped to the view so the glass
|
|
62
62
|
search pills (#mapQ, #mineQ) keep their own look. */
|
|
63
63
|
.panel{display:none;}
|
|
64
64
|
.panel.on{display:block;animation:fadeStep .3s ease both;}
|
|
@@ -248,6 +248,22 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
248
248
|
and faint ink only reaches 3.96:1 on it (task 1002749). */
|
|
249
249
|
.ghrow .priv{margin-left:auto;font-size:11px;font-weight:600;color:var(--ink-soft);background:color-mix(in srgb,var(--ink) 8%,transparent);border-radius:5px;padding:1px 6px;}
|
|
250
250
|
|
|
251
|
+
/* the type declaration (task 1002772): five selectable pills + a description
|
|
252
|
+
card themed per type via a component-scoped custom property (C1 — never a
|
|
253
|
+
page-local root token). No dark rules of its own: every surface is a token
|
|
254
|
+
or a color-mix over one, so both modes carry it (C2/C4). */
|
|
255
|
+
.typeCards{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;}
|
|
256
|
+
.tpill{cursor:pointer;font-family:var(--font-body);font-weight:700;font-size:13.5px;color:var(--ink-soft);
|
|
257
|
+
background:var(--bg-card);border:2px solid var(--line);border-radius:999px;padding:9px 16px;
|
|
258
|
+
transition:all .12s ease;}
|
|
259
|
+
.tpill:hover{border-color:var(--accent);color:var(--accent-deep);}
|
|
260
|
+
.tpill[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:#fff;}
|
|
261
|
+
.typeCard{border:2px solid color-mix(in srgb,var(--type-acc) 35%,var(--bg-card));border-radius:18px;
|
|
262
|
+
padding:16px 18px;margin-bottom:6px;
|
|
263
|
+
background:linear-gradient(180deg,color-mix(in srgb,var(--type-acc) 9%,var(--bg-card)) 0%,var(--bg-card) 100%);}
|
|
264
|
+
.typeCard b{display:block;font-family:var(--font-display);font-size:17px;color:color-mix(in srgb,var(--type-acc) 40%,var(--ink));margin-bottom:4px;}
|
|
265
|
+
.typeCard p{font-size:13.5px;font-weight:500;color:var(--ink-soft);line-height:1.55;}
|
|
266
|
+
|
|
251
267
|
/* 840px matches cosmos's createSplit collapse, the breakpoint the old /create
|
|
252
268
|
used (860) — the two-up grids and the bongo cell fold together */
|
|
253
269
|
@media (max-width:840px){.row2,.choices{grid-template-columns:1fr;}}
|
|
@@ -384,6 +400,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
384
400
|
<thead>
|
|
385
401
|
<tr>
|
|
386
402
|
<th class="colName" data-sort="name" aria-sort="ascending">Project</th>
|
|
403
|
+
<th class="colType">Type</th>
|
|
387
404
|
<th class="right" data-sort="builders">Builders</th>
|
|
388
405
|
<th class="colTags">Runs</th>
|
|
389
406
|
<th class="right colStatus">Status</th>
|
|
@@ -400,6 +417,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
400
417
|
<td class="colName"><span class="pname"><span class="pdot" style="--acc:var(--accent)"></span>
|
|
401
418
|
<span><span class="ptitle" data-brand-product-inline>Cloud Bongos</span><span id="selfPill"></span><br>
|
|
402
419
|
<span class="ptag">this platform</span></span></span></td>
|
|
420
|
+
<td class="colType">—</td>
|
|
403
421
|
<td class="right num" id="selfBuilders">—</td>
|
|
404
422
|
<td class="colTags" id="selfTags">—</td>
|
|
405
423
|
<td class="right colStatus"><span class="chip calm"><span class="cdot"></span>—</span></td>
|
|
@@ -439,6 +457,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
439
457
|
authoritative-looking element of the page reads as broken, not
|
|
440
458
|
honest (task 1002778). -->
|
|
441
459
|
<dl class="cardMeta" id="cardMeta" hidden>
|
|
460
|
+
<dt id="cardTypeL" hidden>Type</dt><dd id="cardType" hidden></dd>
|
|
442
461
|
<dt id="cardBuildersL" hidden>Builders</dt><dd id="cardBuilders" hidden></dd>
|
|
443
462
|
<dt id="cardRunsL" hidden>Runs</dt><dd id="cardRuns" hidden></dd>
|
|
444
463
|
<dt id="cardOwnerL" hidden>Owner</dt><dd id="cardOwner" hidden></dd>
|
|
@@ -507,7 +526,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
507
526
|
|
|
508
527
|
<!-- ══════════════════ VIEW · NEW PROJECT ══════════════════════════════════
|
|
509
528
|
The whole create wizard, absorbed from /create (task 1002763; direction
|
|
510
|
-
§4 — that route now 302s here).
|
|
529
|
+
§4 — that route now 302s here). Seven panels behind the five-beat rail; the
|
|
511
530
|
wizard block in the script below drives them. Same trust boundary as
|
|
512
531
|
ever: every button here only ENQUEUES a provisioning request (ADR 0111).
|
|
513
532
|
The name panel carries a static `on` so a no-script visitor still reads
|
|
@@ -517,9 +536,10 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
517
536
|
<div class="card">
|
|
518
537
|
<ol class="steps" aria-label="Creation steps">
|
|
519
538
|
<li class="now" data-step="1"><span class="snum">1</span><span class="slbl">Name</span></li>
|
|
520
|
-
<li data-step="2"><span class="snum">2</span><span class="slbl">
|
|
521
|
-
<li data-step="3"><span class="snum">3</span><span class="slbl">
|
|
522
|
-
<li data-step="4"><span class="snum">4</span><span class="slbl">
|
|
539
|
+
<li data-step="2"><span class="snum">2</span><span class="slbl">Type</span></li>
|
|
540
|
+
<li data-step="3"><span class="snum">3</span><span class="slbl">Home</span></li>
|
|
541
|
+
<li data-step="4"><span class="snum">4</span><span class="slbl">Address</span></li>
|
|
542
|
+
<li data-step="5"><span class="snum">5</span><span class="slbl">Create</span></li>
|
|
523
543
|
</ol>
|
|
524
544
|
|
|
525
545
|
<div class="createSplit">
|
|
@@ -566,11 +586,39 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
566
586
|
and capture-resistant by design.</p>
|
|
567
587
|
</section>
|
|
568
588
|
|
|
569
|
-
<!-- step 2 ·
|
|
589
|
+
<!-- step 2 · type (task 1002772) — a DECLARATION, closed vocabulary
|
|
590
|
+
(owner decision 2026-08-13): Game / Research / Business /
|
|
591
|
+
Non-Profit / Not Sure, selectable buttons with a themed
|
|
592
|
+
description card. Not Sure is the escape hatch — nobody is
|
|
593
|
+
forced into a wrong answer, and it can change later. -->
|
|
594
|
+
<section class="panel" id="panel-type" aria-labelledby="type-h">
|
|
595
|
+
<h1 id="type-h">What kind of project is it?</h1>
|
|
596
|
+
<p class="lede">Pick the shape that fits best — it labels the project on the map. Not sure is a fine answer.</p>
|
|
597
|
+
<form data-next="3">
|
|
598
|
+
<div class="typeCards" id="typeCards" role="group" aria-label="Project type">
|
|
599
|
+
<button type="button" class="tpill" data-type="game" aria-pressed="false">Game</button>
|
|
600
|
+
<button type="button" class="tpill" data-type="research" aria-pressed="false">Research</button>
|
|
601
|
+
<button type="button" class="tpill" data-type="business" aria-pressed="false">Business</button>
|
|
602
|
+
<button type="button" class="tpill" data-type="non-profit" aria-pressed="false">Non-Profit</button>
|
|
603
|
+
<button type="button" class="tpill" data-type="not-sure" aria-pressed="false">Not Sure</button>
|
|
604
|
+
</div>
|
|
605
|
+
<div class="typeCard" id="typeCard" hidden aria-live="polite">
|
|
606
|
+
<b id="typeCardName"></b>
|
|
607
|
+
<p id="typeCardDesc"></p>
|
|
608
|
+
</div>
|
|
609
|
+
<div class="hint" id="h-projtype"> </div>
|
|
610
|
+
<div class="nav">
|
|
611
|
+
<button type="button" class="btn ghost" data-back="1">← Back</button>
|
|
612
|
+
<button type="submit" class="btn">Next →</button>
|
|
613
|
+
</div>
|
|
614
|
+
</form>
|
|
615
|
+
</section>
|
|
616
|
+
|
|
617
|
+
<!-- step 3 · home (repo + mode) -->
|
|
570
618
|
<section class="panel" id="panel-home" aria-labelledby="home-h">
|
|
571
619
|
<h1 id="home-h">Where the code lives</h1>
|
|
572
620
|
<p class="lede">Every project is a real GitHub repository that you own. Start from a clean slate, or bring a repo you already have.</p>
|
|
573
|
-
<form data-next="
|
|
621
|
+
<form data-next="4">
|
|
574
622
|
<div class="choices" role="group" aria-label="Starting point">
|
|
575
623
|
<button type="button" class="choice" id="c-green" aria-pressed="true">
|
|
576
624
|
<!-- token-built, not platform orange — public-landing/ ships wholesale to
|
|
@@ -629,19 +677,19 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
629
677
|
</div>
|
|
630
678
|
<div class="hint" id="h-home"> </div>
|
|
631
679
|
<div class="nav">
|
|
632
|
-
<button type="button" class="btn ghost" data-back="
|
|
680
|
+
<button type="button" class="btn ghost" data-back="2">← Back</button>
|
|
633
681
|
<button type="submit" class="btn">Next →</button>
|
|
634
682
|
</div>
|
|
635
683
|
</form>
|
|
636
684
|
</section>
|
|
637
685
|
|
|
638
|
-
<!-- step
|
|
686
|
+
<!-- step 4 · address — a CONFIRMATION, not a form (task 1002704: every project
|
|
639
687
|
gets its slug subdomain assigned up front; opt-out lives in the disclosure).
|
|
640
688
|
The domainless default was a proven dead-end (QA walk, task 1002702). -->
|
|
641
689
|
<section class="panel" id="panel-address" aria-labelledby="addr-h">
|
|
642
690
|
<h1 id="addr-h">Its address in the sky</h1>
|
|
643
691
|
<p class="lede">The handle names your project on the platform — and its address comes with it. Sign-in, sharing, everything just works from the moment it stands up.</p>
|
|
644
|
-
<form data-next="
|
|
692
|
+
<form data-next="5">
|
|
645
693
|
<label class="wizField"><span>Project handle</span>
|
|
646
694
|
<input type="text" id="f-slug" placeholder="mercury" autocomplete="off" maxlength="40" spellcheck="false">
|
|
647
695
|
<div class="hint" id="h-slug"> </div>
|
|
@@ -663,13 +711,13 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
663
711
|
</details>
|
|
664
712
|
</div>
|
|
665
713
|
<div class="nav">
|
|
666
|
-
<button type="button" class="btn ghost" data-back="
|
|
714
|
+
<button type="button" class="btn ghost" data-back="3">← Back</button>
|
|
667
715
|
<button type="submit" class="btn">Next →</button>
|
|
668
716
|
</div>
|
|
669
717
|
</form>
|
|
670
718
|
</section>
|
|
671
719
|
|
|
672
|
-
<!-- step
|
|
720
|
+
<!-- step 5 · review + create -->
|
|
673
721
|
<section class="panel" id="panel-review" aria-labelledby="rev-h">
|
|
674
722
|
<h1 id="rev-h">Ready to roll</h1>
|
|
675
723
|
<p class="lede">One tap files the request and the platform starts standing your project up — server, database, address. Here's the beat you're about to drop:</p>
|
|
@@ -677,7 +725,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
677
725
|
<div class="alert" id="createAlert" role="alert"></div>
|
|
678
726
|
<div class="nav" style="flex-direction:column;align-items:stretch;">
|
|
679
727
|
<button type="button" class="btn big" id="doCreate">Create my project</button>
|
|
680
|
-
<button type="button" class="btn ghost" data-back="
|
|
728
|
+
<button type="button" class="btn ghost" data-back="4" style="align-self:flex-start;margin-top:10px;">← Back</button>
|
|
681
729
|
</div>
|
|
682
730
|
<p class="finePrint">This files the request; the control plane does the real standup and nothing touches servers from this page. You'll get the few steps only you can do right after.</p>
|
|
683
731
|
</section>
|
|
@@ -1029,6 +1077,25 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
1029
1077
|
if (tags.length > TAG_CAP) shown.push('<span>+' + (tags.length - TAG_CAP) + '</span>');
|
|
1030
1078
|
return '<span class="tags">' + shown.join('') + '</span>';
|
|
1031
1079
|
}
|
|
1080
|
+
/* The declared-type vocabulary (task 1002772; owner decision 2026-08-13).
|
|
1081
|
+
One formatter for the table cell + the card, like tagsHtml: null means
|
|
1082
|
+
"not declared" and renders an em dash — never a guess. The per-type accent
|
|
1083
|
+
reuses the ORB_ACCENTS five-token palette (component-scoped, C1). */
|
|
1084
|
+
var TYPE_META = {
|
|
1085
|
+
'game': { label: 'Game', acc: 'var(--accent)', desc: 'A world to play in. Agents build the world, the rules, and the play itself.' },
|
|
1086
|
+
'research': { label: 'Research', acc: 'var(--sea)', desc: 'A question being worked. Agents gather the evidence, run the experiments, and write up what holds — the repo is the lab notebook.' },
|
|
1087
|
+
'business': { label: 'Business', acc: 'var(--terracotta)', desc: 'A venture with customers. Agents build the product and the plumbing behind it.' },
|
|
1088
|
+
'non-profit': { label: 'Non-Profit', acc: 'var(--gold-soft)', desc: 'A cause, not a profit line. Agents build for a mission and the community around it.' },
|
|
1089
|
+
'not-sure': { label: 'Not Sure', acc: 'var(--rule-soft)', desc: 'Still finding its shape. Pick this now — nothing depends on it.' }
|
|
1090
|
+
};
|
|
1091
|
+
function typeLabel(t) {
|
|
1092
|
+
var m = TYPE_META[t];
|
|
1093
|
+
return m ? m.label : '';
|
|
1094
|
+
}
|
|
1095
|
+
function typeHtml(t) {
|
|
1096
|
+
var l = typeLabel(t);
|
|
1097
|
+
return l ? '<span class="typeTag">' + l + '</span>' : '—';
|
|
1098
|
+
}
|
|
1032
1099
|
function matchesMapQuery(p, q) {
|
|
1033
1100
|
if (!q) return true;
|
|
1034
1101
|
/* an owned entry also answers by slug and address — the same fields the
|
|
@@ -1116,6 +1183,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
1116
1183
|
unreachable — and an em dash says exactly that. 0 is a real answer and
|
|
1117
1184
|
renders as 0, because "nobody has joined yet" is a different fact from
|
|
1118
1185
|
"we could not ask". */
|
|
1186
|
+
'<td class="colType">' + typeHtml(p.type) + '</td>' +
|
|
1119
1187
|
'<td class="right num">' + (p.builders == null ? '—' : esc(String(p.builders))) + '</td>' +
|
|
1120
1188
|
'<td class="colTags">' + tagsHtml(p.tags) + '</td>' +
|
|
1121
1189
|
/* liveness on a CATALOG row is NOT probed by this surface — a chip
|
|
@@ -1146,6 +1214,9 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
1146
1214
|
var h = hostOf(p.origin);
|
|
1147
1215
|
catalogHosts[h] = 1;
|
|
1148
1216
|
p._inst = ownedByHost[h] || null;
|
|
1217
|
+
/* the catalog may predate the declaration the owner filed with the
|
|
1218
|
+
create — their own instance row is a truthful source, never a guess */
|
|
1219
|
+
if (p.type == null && p._inst && p._inst.type) p.type = p._inst.type;
|
|
1149
1220
|
});
|
|
1150
1221
|
var browse = catalog.slice();
|
|
1151
1222
|
ownedInstances.forEach(function (inst) {
|
|
@@ -1154,7 +1225,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
1154
1225
|
browse.push({
|
|
1155
1226
|
name: inst.slug, tagline: inst.domain || '',
|
|
1156
1227
|
origin: inst.domain ? 'https://' + inst.domain : '',
|
|
1157
|
-
builders: null, tags: null,
|
|
1228
|
+
builders: null, tags: null, type: inst.type || null,
|
|
1158
1229
|
_inst: inst, _acc: ORB_ACCENTS[slugAccent(inst.slug)]
|
|
1159
1230
|
});
|
|
1160
1231
|
});
|
|
@@ -1282,7 +1353,8 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
1282
1353
|
/* builders: 0 is a real answer ("nobody has joined yet"), null is "we could
|
|
1283
1354
|
not ask" and hides. runs: [] is a real answer too — the project declares
|
|
1284
1355
|
nothing beyond the core set, which the owner voiced as "core". */
|
|
1285
|
-
var any = cardFact('
|
|
1356
|
+
var any = cardFact('cardType', typeLabel(p.type));
|
|
1357
|
+
any = cardFact('cardBuilders', p.builders == null ? '' : String(p.builders)) || any;
|
|
1286
1358
|
any = cardFact('cardRuns', Array.isArray(p.tags) ? tagsHtml(p.tags) : '', true) || any;
|
|
1287
1359
|
/* when the rel chip already says "yours", naming the viewer as Owner is
|
|
1288
1360
|
noise — the fact answers strangers, not the person it describes */
|
|
@@ -1929,9 +2001,13 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
1929
2001
|
var root = $('view-new');
|
|
1930
2002
|
|
|
1931
2003
|
/* ── state ── */
|
|
1932
|
-
|
|
2004
|
+
/* -v2 since task 1002772: the step indices renumbered (Type joined as 2),
|
|
2005
|
+
so a draft written under the five-step numbering must be ignored, not
|
|
2006
|
+
reinterpreted — a stale step 4 would land on Address and skip the Type
|
|
2007
|
+
gate with the rail already marking it done. */
|
|
2008
|
+
var DRAFT_KEY = 'cb-create-draft-v2';
|
|
1933
2009
|
var CREATED_KEY = 'cb-created-instance';
|
|
1934
|
-
var state = { step: 0, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
|
|
2010
|
+
var state = { step: 0, name: '', projType: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
|
|
1935
2011
|
try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
|
|
1936
2012
|
var authKnown = false; /* flips true when the boot's /me settles, either way */
|
|
1937
2013
|
|
|
@@ -1945,7 +2021,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
1945
2021
|
state.name = handedName.trim().slice(0, 80);
|
|
1946
2022
|
state.slug = deriveSlug(state.name); state.slugTouched = false;
|
|
1947
2023
|
state.repo = deriveRepo(state.name); state.repoTouched = false;
|
|
1948
|
-
state.step = state.step >= 1 && state.step <=
|
|
2024
|
+
state.step = state.step >= 1 && state.step <= 5 ? state.step : 1;
|
|
1949
2025
|
try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
|
|
1950
2026
|
}
|
|
1951
2027
|
} catch (e) {}
|
|
@@ -2044,8 +2120,10 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2044
2120
|
try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
|
|
2045
2121
|
}
|
|
2046
2122
|
|
|
2047
|
-
/* ── step navigation ──
|
|
2048
|
-
|
|
2123
|
+
/* ── step navigation ──
|
|
2124
|
+
Six steps since task 1002772: gate 0 · name 1 · type 2 · home 3 ·
|
|
2125
|
+
address 4 · review 5 · done 6 (the rail shows 1-5; done marks it all). */
|
|
2126
|
+
var panels = { 0: 'panel-gate', 1: 'panel-name', 2: 'panel-type', 3: 'panel-home', 4: 'panel-address', 5: 'panel-review', 6: 'panel-done' };
|
|
2049
2127
|
function show(step, moveFocus) {
|
|
2050
2128
|
state.step = step;
|
|
2051
2129
|
Object.keys(panels).forEach(function (k) {
|
|
@@ -2054,11 +2132,11 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2054
2132
|
root.querySelectorAll('.steps li').forEach(function (li) {
|
|
2055
2133
|
var n = Number(li.getAttribute('data-step'));
|
|
2056
2134
|
li.classList.toggle('now', n === step);
|
|
2057
|
-
li.classList.toggle('done', n < step || step ===
|
|
2135
|
+
li.classList.toggle('done', n < step || step === 6);
|
|
2058
2136
|
});
|
|
2059
2137
|
saveDraft();
|
|
2060
2138
|
/* the done step takes the whole card — the bongo pair steps aside */
|
|
2061
|
-
root.querySelector('.createSplit').classList.toggle('wizDone', step ===
|
|
2139
|
+
root.querySelector('.createSplit').classList.toggle('wizDone', step === 6);
|
|
2062
2140
|
/* focus moves ONLY on a deliberate step (the hub rule setView already
|
|
2063
2141
|
applies to this view: never on boot, never on a background settle) */
|
|
2064
2142
|
if (moveFocus) {
|
|
@@ -2068,9 +2146,9 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2068
2146
|
/* the repo probe is authenticated — never fire it before /me settles
|
|
2069
2147
|
(a pre-auth probe 401s for strangers and, worse, could seed the
|
|
2070
2148
|
draft's owner with myLogin()'s placeholder) */
|
|
2071
|
-
if (step ===
|
|
2072
|
-
if (step ===
|
|
2073
|
-
if (step ===
|
|
2149
|
+
if (step === 3) { syncHome(); if (authKnown) ensureRepoAccess(); }
|
|
2150
|
+
if (step === 4) renderAddrPanel();
|
|
2151
|
+
if (step === 5) renderReview();
|
|
2074
2152
|
}
|
|
2075
2153
|
|
|
2076
2154
|
/* ── field wiring ── */
|
|
@@ -2118,8 +2196,35 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2118
2196
|
$('c-adopt').addEventListener('click', function () { setMode('adopt'); });
|
|
2119
2197
|
setMode(state.mode);
|
|
2120
2198
|
|
|
2199
|
+
/* ── the type declaration (task 1002772) ── selecting a pill fills the
|
|
2200
|
+
description card, themed per type via --type-acc (component-scoped, C1) */
|
|
2201
|
+
function paintType(t) {
|
|
2202
|
+
root.querySelectorAll('#typeCards .tpill').forEach(function (b) {
|
|
2203
|
+
b.setAttribute('aria-pressed', String(b.getAttribute('data-type') === t));
|
|
2204
|
+
});
|
|
2205
|
+
var card = $('typeCard');
|
|
2206
|
+
var m = t && TYPE_META[t];
|
|
2207
|
+
if (m) {
|
|
2208
|
+
card.hidden = false;
|
|
2209
|
+
card.style.setProperty('--type-acc', m.acc);
|
|
2210
|
+
$('typeCardName').textContent = m.label;
|
|
2211
|
+
$('typeCardDesc').textContent = m.desc;
|
|
2212
|
+
} else {
|
|
2213
|
+
card.hidden = true;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
root.querySelectorAll('#typeCards .tpill').forEach(function (b) {
|
|
2217
|
+
b.addEventListener('click', function () {
|
|
2218
|
+
state.projType = b.getAttribute('data-type');
|
|
2219
|
+
$('h-projtype').innerHTML = ' '; $('h-projtype').className = 'hint';
|
|
2220
|
+
paintType(state.projType);
|
|
2221
|
+
saveDraft();
|
|
2222
|
+
});
|
|
2223
|
+
});
|
|
2224
|
+
paintType(state.projType || null);
|
|
2225
|
+
|
|
2121
2226
|
/* ── GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099 / ADR 0143.
|
|
2122
|
-
Progressive enhancement: probe GET /github/repos once we reach step
|
|
2227
|
+
Progressive enhancement: probe GET /github/repos once we reach the Home step.
|
|
2123
2228
|
200 → we hold repo access → show the picker (adopt) / name field (fresh);
|
|
2124
2229
|
403 → offer to grant repo access (a ?repos=1 re-sign-in);
|
|
2125
2230
|
503 (server can't store the token) or any error → the manual inputs.
|
|
@@ -2134,7 +2239,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2134
2239
|
|
|
2135
2240
|
function connectRepos(e) {
|
|
2136
2241
|
if (e) e.preventDefault();
|
|
2137
|
-
/* Same-tab navigation so the sessionStorage draft round-trips back to step
|
|
2242
|
+
/* Same-tab navigation so the sessionStorage draft round-trips back to the Home step. */
|
|
2138
2243
|
window.location.assign(API + '/auth/web/start?return=/projects%3Fview%3Dnew&repos=1');
|
|
2139
2244
|
}
|
|
2140
2245
|
$('ghConnectBtn').addEventListener('click', connectRepos);
|
|
@@ -2292,6 +2397,14 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2292
2397
|
return true;
|
|
2293
2398
|
}
|
|
2294
2399
|
if (step === 2) {
|
|
2400
|
+
if (!state.projType) {
|
|
2401
|
+
$('h-projtype').textContent = 'Pick one — Not Sure is a fine answer.';
|
|
2402
|
+
$('h-projtype').className = 'hint err';
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
return true;
|
|
2406
|
+
}
|
|
2407
|
+
if (step === 3) {
|
|
2295
2408
|
var homeErr = function (msg) { $('h-home').textContent = msg; $('h-home').className = 'hint err'; return false; };
|
|
2296
2409
|
if (repoAccess === 'need-connect') return homeErr('Allow GitHub repo access to pick or create your repository — or choose “enter it manually”.');
|
|
2297
2410
|
if (repoAccess === true && state.mode !== 'adopt') {
|
|
@@ -2306,7 +2419,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2306
2419
|
$('h-home').innerHTML = ' '; $('h-home').className = 'hint';
|
|
2307
2420
|
return true;
|
|
2308
2421
|
}
|
|
2309
|
-
if (step ===
|
|
2422
|
+
if (step === 4) {
|
|
2310
2423
|
if (!validSlug(state.slug)) {
|
|
2311
2424
|
$('h-slug').textContent = '2–40 lowercase letters, numbers, or dashes; no leading/trailing/double dash.';
|
|
2312
2425
|
$('h-slug').className = 'hint err'; $('f-slug').classList.add('bad');
|
|
@@ -2347,6 +2460,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2347
2460
|
cb.disabled = false; cb.textContent = 'Create my project';
|
|
2348
2461
|
var rows = [
|
|
2349
2462
|
['Project', esc(state.name)],
|
|
2463
|
+
['Type', typeLabel(state.projType) || 'Not declared'],
|
|
2350
2464
|
['Starting from', state.mode === 'adopt' ? 'A repo you already have' : 'A fresh, empty repo'],
|
|
2351
2465
|
['Repository', '<code>' + esc(state.owner + '/' + state.repo) + '</code>'],
|
|
2352
2466
|
['Handle', '<code>' + esc(state.slug) + '</code>'],
|
|
@@ -2415,6 +2529,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2415
2529
|
headers: { 'Content-Type': 'application/json' },
|
|
2416
2530
|
body: JSON.stringify({
|
|
2417
2531
|
slug: state.slug,
|
|
2532
|
+
type: state.projType || undefined,
|
|
2418
2533
|
hosting_shape: 'standalone',
|
|
2419
2534
|
domain: resolvedDomain() || undefined,
|
|
2420
2535
|
target_ref: state.owner + '/' + state.repo,
|
|
@@ -2444,7 +2559,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2444
2559
|
sessionStorage.removeItem(DRAFT_KEY);
|
|
2445
2560
|
} catch (e) {}
|
|
2446
2561
|
renderDone();
|
|
2447
|
-
show(
|
|
2562
|
+
show(6, true);
|
|
2448
2563
|
startProgress(data.instance && data.instance.id);
|
|
2449
2564
|
}).catch(function (err) {
|
|
2450
2565
|
if (String(err && err.message) === 'signing in') return;
|
|
@@ -2452,7 +2567,7 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2452
2567
|
if (err && err.gotoHome) {
|
|
2453
2568
|
/* A repo-creation problem (name taken / reconnect) — bounce back to the Home
|
|
2454
2569
|
step with the reason, so the user fixes it where the repo is chosen. */
|
|
2455
|
-
show(
|
|
2570
|
+
show(3, true);
|
|
2456
2571
|
$('h-home').textContent = err.homeMsg || 'Please revisit the repository step.';
|
|
2457
2572
|
$('h-home').className = 'hint err';
|
|
2458
2573
|
return;
|
|
@@ -2828,12 +2943,13 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2828
2943
|
live = { instStatus: null, manifest: null, manifestMsg: '' };
|
|
2829
2944
|
lastStepsHtml = '';
|
|
2830
2945
|
try { sessionStorage.removeItem(CREATED_KEY); sessionStorage.removeItem(DRAFT_KEY); } catch (e) {}
|
|
2831
|
-
state = { step: 1, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
|
|
2946
|
+
state = { step: 1, name: '', projType: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
|
|
2832
2947
|
$('newName').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
|
|
2833
2948
|
$('f-noaddr').checked = false;
|
|
2834
2949
|
/* Re-seed the owner from the signed-in account for the next project. */
|
|
2835
2950
|
if (me && me.github_login) { state.owner = me.github_login; $('f-owner').value = state.owner; }
|
|
2836
2951
|
$('h-name').innerHTML = ' ';
|
|
2952
|
+
paintType(null);
|
|
2837
2953
|
setMode('greenfield');
|
|
2838
2954
|
show(1, true);
|
|
2839
2955
|
});
|
|
@@ -2858,15 +2974,15 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
2858
2974
|
state.name = created.name || created.slug; state.slug = created.slug || '';
|
|
2859
2975
|
state.domain = created.domain || ''; state.mode = created.mode || 'greenfield';
|
|
2860
2976
|
renderDone();
|
|
2861
|
-
show(
|
|
2977
|
+
show(6, !!focus);
|
|
2862
2978
|
startProgress(created.id);
|
|
2863
2979
|
resumePolls();
|
|
2864
2980
|
return;
|
|
2865
2981
|
}
|
|
2866
2982
|
/* before identity settles, never render past the name step: deeper steps
|
|
2867
|
-
carry authenticated side effects (the step
|
|
2983
|
+
carry authenticated side effects (the Home-step repo probe), and onAuth()
|
|
2868
2984
|
re-enters to the real draft step the moment /me answers */
|
|
2869
|
-
var step = authKnown && state.step >= 1 && state.step <=
|
|
2985
|
+
var step = authKnown && state.step >= 1 && state.step <= 5 ? state.step : 1;
|
|
2870
2986
|
if (step === 1) {
|
|
2871
2987
|
show(1, false);
|
|
2872
2988
|
if (focus) $('newName').focus();
|
package/scripts/gds/provision.js
CHANGED
|
@@ -1007,7 +1007,9 @@ async function federateInstance(inst, deps) {
|
|
|
1007
1007
|
ownerGithubId = rows[0] ? rows[0].github_id : null;
|
|
1008
1008
|
} catch { /* non-fatal */ }
|
|
1009
1009
|
await platformIdentity.registerClient(
|
|
1010
|
-
|
|
1010
|
+
// type: the owner's declaration rides the instance row from the create
|
|
1011
|
+
// wizard (task 1002772) and lands on the catalog row here — never guessed.
|
|
1012
|
+
{ clientId, secret: clientSecret, name: inst.slug, redirectUris, origin, ownerGithubId, type: inst.type },
|
|
1011
1013
|
{ pool: db },
|
|
1012
1014
|
);
|
|
1013
1015
|
log(` [federate] registered client '${clientId}' on the hub (origin ${origin})`);
|
|
@@ -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,12 @@ 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)
|
|
662
|
+
// 1.19.117 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
663
|
+
// landed since 1.19.116 with no explicit bump. run 31830559253. (task 1002620)
|
|
660
664
|
// ---------------------------------------------------------------------------
|
|
661
|
-
const CORE_VERSION = '1.19.
|
|
665
|
+
const CORE_VERSION = '1.19.117'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
662
666
|
|
|
663
667
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
664
668
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|