@bongos/core 1.17.3 → 1.18.3
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 +212 -332
- package/.devcontainer/healthcheck.sh +0 -0
- package/.devcontainer/init-firewall.sh +0 -0
- package/.devcontainer/post-create.sh +0 -0
- package/GOVERNANCE.md +1 -1
- package/README.md +26 -22
- package/bin/bongos.js +0 -0
- package/config/branding.neutral.json +5 -1
- package/docker-entrypoint.sh +0 -0
- package/docs/adr/0142-project-context-is-prose-only.md +41 -0
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
- package/docs/adr/README.md +6 -0
- package/docs/api/openapi.json +1630 -632
- package/docs/api-reference.md +50 -15
- package/docs/file-map.md +0 -18
- package/docs/onboarding/diagrams/05-versioning.mmd +4 -6
- package/docs/onboarding/diagrams/assertions.json +5 -9
- package/docs/recipes/packaging-the-core.md +13 -0
- package/docs/recipes/upgrading-the-core.md +5 -2
- package/migrations/039_newcomer_friendly_column.sql +5 -0
- package/migrations/102_remove_box_first_provision_approval.sql +4 -0
- package/migrations/core_192_github_oauth_tokens.sql +33 -0
- package/modules/hall-ui/public/builders.js +37 -0
- package/modules/hall-ui/public/goals-page.js +98 -176
- package/modules/hall-ui/public/goals.css +183 -0
- package/modules/hall-ui/public/goals.html +35 -261
- package/modules/hall-ui/public/goals.js +7 -2
- package/modules/hall-ui/public/hall-widgets.js +12 -4
- package/modules/hall-ui/public/my-projects.html +78 -0
- package/modules/hall-ui/public/my-projects.js +142 -0
- package/modules/hall-ui/public/scouting.html +45 -0
- package/modules/hall-ui/public/scouting.js +73 -0
- package/modules/hall-ui/public/settings.css +355 -0
- package/modules/hall-ui/public/settings.html +86 -414
- package/modules/hall-ui/public/settings.js +96 -35
- package/modules/hall-ui/public/style.css +31 -0
- package/modules/hall-ui/public/watch.css +184 -0
- package/modules/hall-ui/public/watch.html +111 -285
- package/modules/hall-ui/public/watch.js +59 -21
- package/modules/status-ui/public/explore.html +54 -0
- package/modules/status-ui/public/explore.js +67 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +292 -19
- package/public-landing/index.html +11 -76
- package/scripts/cron/style_guide_audit.sh +0 -0
- package/scripts/gds/api.js +0 -0
- package/scripts/gds/architect-audit.js +0 -0
- package/scripts/gds/audit-authorship.sh +0 -0
- package/scripts/gds/audit-replay.sh +0 -0
- package/scripts/gds/box.js +0 -0
- package/scripts/gds/capture.js +0 -0
- package/scripts/gds/cli-lib.js +11 -5
- package/scripts/gds/conductor-dispatch.js +0 -0
- package/scripts/gds/coreB-carve-proof.sh +0 -0
- package/scripts/gds/db-backup-nightly.sh +0 -0
- package/scripts/gds/db-backup-offsite.sh +0 -0
- package/scripts/gds/doctor.js +0 -0
- package/scripts/gds/grade-smoke.js +0 -0
- package/scripts/gds/hooks/pre-push.js +0 -0
- package/scripts/gds/install-git-hooks.js +0 -0
- package/scripts/gds/lint-migrations.js +102 -0
- package/scripts/gds/login.js +42 -10
- package/scripts/gds/main-audit.js +0 -0
- package/scripts/gds/mirror-redact.js +5 -0
- package/scripts/gds/oauth-secret.js +0 -0
- package/scripts/gds/onboarding-analyze.js +0 -0
- package/scripts/gds/onboarding-ship.js +0 -0
- package/scripts/gds/package-core.js +263 -4
- package/scripts/gds/paste-token.js +0 -0
- package/scripts/gds/portability-smoke-art.sh +0 -0
- package/scripts/gds/portability-smoke-memory.sh +0 -0
- package/scripts/gds/provision.js +270 -11
- package/scripts/gds/publish-manifest.js +15 -0
- package/scripts/gds/push-main.js +0 -0
- package/scripts/gds/render-ideas.js +0 -0
- package/scripts/gds/run-unit-tests.js +38 -14
- package/scripts/gds/security-baseline-check.sh +0 -0
- package/scripts/gds/session.js +0 -0
- package/scripts/gds/setup.js +41 -0
- package/scripts/gds/ship.js +126 -32
- package/scripts/gds/smoke-batch-claim.sh +0 -0
- package/scripts/gds/smoke-batch-release.sh +0 -0
- package/scripts/gds/smoke-box.sh +0 -0
- package/scripts/gds/smoke-claim-consistency.sh +0 -0
- package/scripts/gds/smoke-dependencies.sh +0 -0
- package/scripts/gds/smoke-write.sh +0 -0
- package/scripts/gds/upgrade.js +147 -5
- package/scripts/hall-preview/server.js +18 -1
- package/scripts/migrate.sh +10 -0
- package/scripts/migrate_cost_ledger.sh +0 -0
- package/scripts/render-diagrams.sh +0 -0
- package/scripts/security/dep-audit.js +0 -0
- package/scripts/setup-builder.js +0 -0
- package/scripts/setup-builder.sh +0 -0
- package/scripts/snapshots/maybe-render-on-deploy.sh +0 -0
- package/src/bongos/auth.js +362 -5
- package/src/bongos/db.js +66 -0
- package/src/bongos/github-repos.js +93 -0
- package/src/bongos/routes/auth.js +93 -11
- package/src/bongos/routes/github.js +90 -0
- package/src/bongos/routes/instance.js +15 -3
- package/src/bongos/routes.js +2 -0
- package/src/branding.js +17 -2
- package/src/instance-config.js +16 -0
- package/src/module-api.js +48 -1
- package/tests/branding.mjs +29 -23
- package/tests/github_repos_helpers.mjs +91 -0
- package/tests/github_token_store.mjs +121 -0
- package/tests/idp_device_exchange.mjs +105 -0
- package/tests/idp_federation.mjs +100 -0
- package/tests/migrations_forward_safe.mjs +86 -0
- package/tests/module_api.mjs +1 -1
- package/tests/package_core.mjs +246 -1
- package/tests/provision.mjs +226 -0
- package/tests/pruned_lockfile_integrity.mjs +79 -0
- package/tests/ship_ci_deploy.mjs +60 -0
- package/tests/upgrade.mjs +254 -5
- package/.gitignore +0 -91
- package/scripts/hall-preview/fixtures/access-requests.json +0 -1
- package/scripts/hall-preview/fixtures/achievements.json +0 -76
- package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
- package/scripts/hall-preview/fixtures/audit-log.json +0 -1
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
- package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
- package/scripts/hall-preview/fixtures/builders__roster.json +0 -1
- package/scripts/hall-preview/fixtures/goals.json +0 -502
- package/scripts/hall-preview/fixtures/goals__25.json +0 -96
- package/scripts/hall-preview/fixtures/goals__26.json +0 -67
- package/scripts/hall-preview/fixtures/goals__3.json +0 -103
- package/scripts/hall-preview/fixtures/goals__35.json +0 -49
- package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
- package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
- package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
- package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
- package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -1
- package/scripts/hall-preview/fixtures/inbox.json +0 -330
- package/scripts/hall-preview/fixtures/instance.json +0 -124
- package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
- package/scripts/hall-preview/fixtures/me.json +0 -476
- package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
- package/scripts/hall-preview/fixtures/override-requests.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
- package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
- package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
- package/scripts/hall-preview/fixtures/public__grades.json +0 -50
- package/scripts/hall-preview/fixtures/public__progress.json +0 -124
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
- package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
- package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
- package/scripts/hall-preview/fixtures/security__docs.json +0 -1
- package/scripts/hall-preview/fixtures/security__reports.json +0 -1
- package/scripts/hall-preview/fixtures/sessions__search.json +0 -1
- package/scripts/hall-preview/fixtures/tasks.json +0 -2420
- package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
- package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
- package/scripts/hall-preview/fixtures/versions.json +0 -89
package/tests/ship_ci_deploy.mjs
CHANGED
|
@@ -71,6 +71,66 @@ test('parseDeployMode: SAFE DEFAULT laptop on no input at all', () => {
|
|
|
71
71
|
assert.equal(parseDeployMode('', ''), 'laptop');
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
// ---- 1b. resolveDeployTarget precedence + safe "no target" default (task 1002202) ----
|
|
75
|
+
// Every case passes env explicitly so the machine's own OTB_/GDS_ env can't leak in.
|
|
76
|
+
|
|
77
|
+
const { resolveDeployTarget } = ship;
|
|
78
|
+
|
|
79
|
+
test('resolveDeployTarget: SAFE DEFAULT — no env, no config, no branding → no target', () => {
|
|
80
|
+
const r = resolveDeployTarget({ env: {}, configRaw: null, branding: null });
|
|
81
|
+
assert.equal(r.sshTarget, null);
|
|
82
|
+
assert.equal(r.healthzUrl, null);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('resolveDeployTarget: config sshTarget + healthzUrl are honored', () => {
|
|
86
|
+
const r = resolveDeployTarget({
|
|
87
|
+
env: {},
|
|
88
|
+
configRaw: '{"sshTarget":"deployer@host.example","healthzUrl":"https://inst.example/healthz"}',
|
|
89
|
+
branding: null,
|
|
90
|
+
});
|
|
91
|
+
assert.equal(r.sshTarget, 'deployer@host.example');
|
|
92
|
+
assert.equal(r.healthzUrl, 'https://inst.example/healthz');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('resolveDeployTarget: env wins over config for the ssh target', () => {
|
|
96
|
+
const r = resolveDeployTarget({
|
|
97
|
+
env: { GDS_DEPLOY_SSH_TARGET: 'env@host.example' },
|
|
98
|
+
configRaw: '{"sshTarget":"config@host.example"}',
|
|
99
|
+
branding: null,
|
|
100
|
+
});
|
|
101
|
+
assert.equal(r.sshTarget, 'env@host.example');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('resolveDeployTarget: healthzUrl falls back to branding publicOrigin + /healthz', () => {
|
|
105
|
+
const r = resolveDeployTarget({
|
|
106
|
+
env: {},
|
|
107
|
+
configRaw: '{"sshTarget":"deployer@host.example"}',
|
|
108
|
+
branding: { domains: { publicOrigin: 'https://cloudbongos.com' } },
|
|
109
|
+
});
|
|
110
|
+
assert.equal(r.healthzUrl, 'https://cloudbongos.com/healthz');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('resolveDeployTarget: env healthz URL wins over the branding fallback', () => {
|
|
114
|
+
const r = resolveDeployTarget({
|
|
115
|
+
env: { GDS_DEPLOY_HEALTHZ_URL: 'https://explicit.example/healthz' },
|
|
116
|
+
configRaw: null,
|
|
117
|
+
branding: { domains: { publicOrigin: 'https://cloudbongos.com' } },
|
|
118
|
+
});
|
|
119
|
+
assert.equal(r.healthzUrl, 'https://explicit.example/healthz');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('resolveDeployTarget: malformed config JSON → safe no-target (never throws)', () => {
|
|
123
|
+
const r = resolveDeployTarget({ env: {}, configRaw: '{not json', branding: null });
|
|
124
|
+
assert.equal(r.sshTarget, null);
|
|
125
|
+
assert.equal(r.healthzUrl, null);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('resolveDeployTarget: blank/whitespace config values are treated as unset', () => {
|
|
129
|
+
const r = resolveDeployTarget({ env: {}, configRaw: '{"sshTarget":" ","healthzUrl":""}', branding: null });
|
|
130
|
+
assert.equal(r.sshTarget, null);
|
|
131
|
+
assert.equal(r.healthzUrl, null);
|
|
132
|
+
});
|
|
133
|
+
|
|
74
134
|
test('parseDeployMode: SAFE DEFAULT laptop on malformed config JSON', () => {
|
|
75
135
|
assert.equal(parseDeployMode(undefined, '{not json'), 'laptop');
|
|
76
136
|
});
|
package/tests/upgrade.mjs
CHANGED
|
@@ -16,10 +16,11 @@ import { strict as assert } from 'node:assert';
|
|
|
16
16
|
import { createRequire } from 'node:module';
|
|
17
17
|
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
18
18
|
import { tmpdir } from 'node:os';
|
|
19
|
-
import { join } from 'node:path';
|
|
19
|
+
import { join, dirname } from 'node:path';
|
|
20
20
|
|
|
21
21
|
const require = createRequire(import.meta.url);
|
|
22
22
|
const u = require('../scripts/gds/upgrade.js');
|
|
23
|
+
const pc = require('../scripts/gds/package-core.js'); // the packager — reuse its EXACT hash fns to build reference pins
|
|
23
24
|
|
|
24
25
|
let passed = 0, failed = 0;
|
|
25
26
|
const pending = [];
|
|
@@ -159,7 +160,9 @@ t('full: pre-flight → pin → install → migrate → materialize → restart
|
|
|
159
160
|
let materialized = null;
|
|
160
161
|
let regenArgs = null;
|
|
161
162
|
const res = await u.runUpgrade(
|
|
162
|
-
|
|
163
|
+
// healthCheck:false — this test exercises the full pipeline, not the health poll (which would need
|
|
164
|
+
// a fetch mock); a managed-restart bump now requires a --health-url unless health is opted out (H9).
|
|
165
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'emersonian.service', healthCheck: false },
|
|
163
166
|
{
|
|
164
167
|
log: () => {}, err: () => {},
|
|
165
168
|
run: fakeRun(calls, { dirty: '', installTo: '1.15.0', dir }),
|
|
@@ -398,7 +401,7 @@ t('runUpgrade --registry (full): pins the exact version + writes the env-fed .np
|
|
|
398
401
|
const dir = scratchConsumer({ pinned: '1.16.0', installed: '1.16.0' });
|
|
399
402
|
const calls = [];
|
|
400
403
|
const res = await u.runUpgrade(
|
|
401
|
-
{ to: '1.17.0', registry: true, instance: dir, service: 'demo.service' },
|
|
404
|
+
{ to: '1.17.0', registry: true, instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out (no health poll under test)
|
|
402
405
|
{
|
|
403
406
|
log: () => {}, err: () => {},
|
|
404
407
|
run: fakeRun(calls, { dirty: '', installTo: '1.17.0', dir }),
|
|
@@ -479,7 +482,7 @@ t('rollback on migrate failure: reverts pin + reinstalls previous core + rolled_
|
|
|
479
482
|
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
480
483
|
const calls = [], queries = [];
|
|
481
484
|
const res = await u.runUpgrade(
|
|
482
|
-
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' },
|
|
485
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out — this test is about migrate-failure rollback, not health
|
|
483
486
|
{
|
|
484
487
|
log: () => {}, err: () => {},
|
|
485
488
|
run: pinAwareRun(calls, { dir, failMigrate: true }),
|
|
@@ -502,7 +505,7 @@ t('rollback on npm-install failure: reverts pin + reinstalls previous core', asy
|
|
|
502
505
|
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
503
506
|
const calls = [], queries = [];
|
|
504
507
|
const res = await u.runUpgrade(
|
|
505
|
-
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' },
|
|
508
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out — this test is about install-failure rollback, not health
|
|
506
509
|
{
|
|
507
510
|
log: () => {}, err: () => {},
|
|
508
511
|
run: pinAwareRun(calls, { dir, failInstallForVersion: '1.15.0' }),
|
|
@@ -547,6 +550,252 @@ t('restorePin: writes a previous dep string back verbatim (file: or registry)',
|
|
|
547
550
|
assert.equal(JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).dependencies['@bongos/core'], '^1.16.0');
|
|
548
551
|
});
|
|
549
552
|
|
|
553
|
+
// ---- 7. integrity-pin verification (task 1002216 / audit H8, finding F1) ----
|
|
554
|
+
// Build a reference pin exactly as the packager does — canonicalTreeHash over the pinned
|
|
555
|
+
// files' sha256, so a matching installed tree recomputes byte-identically.
|
|
556
|
+
function pinManifestFor(files, { tarballSha = null, tarballBytes = 0 } = {}) {
|
|
557
|
+
const meta = files
|
|
558
|
+
.map((f) => ({ path: f.path, mode: f.mode || '0000644', sha256: pc.sha256hex(Buffer.from(f.content)) }))
|
|
559
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
560
|
+
const m = { artifact: 'bongos-core', tree_sha256: pc.canonicalTreeHash(meta), files: meta };
|
|
561
|
+
if (tarballSha) m.tarball = { name: 'bongos-core.tgz', sha256: tarballSha, bytes: tarballBytes };
|
|
562
|
+
return m;
|
|
563
|
+
}
|
|
564
|
+
function writeCoreTree(coreDir, files) {
|
|
565
|
+
for (const f of files) {
|
|
566
|
+
const abs = join(coreDir, ...f.path.split('/'));
|
|
567
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
568
|
+
writeFileSync(abs, f.content);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const PIN_FILES = [{ path: 'a.js', content: 'console.log(1)\n' }, { path: 'sub/b.json', content: '{"x":1}\n' }];
|
|
572
|
+
|
|
573
|
+
t('verifyInstalledPin: matching installed tree + tarball bytes → ok (both checks)', () => {
|
|
574
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
575
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
576
|
+
writeCoreTree(coreDir, PIN_FILES);
|
|
577
|
+
const tgzBytes = Buffer.from('deterministic-fake-tgz');
|
|
578
|
+
const tgz = join(dir, 'vendor', 'bongos-core-1.15.0.tgz');
|
|
579
|
+
mkdirSync(dirname(tgz), { recursive: true });
|
|
580
|
+
writeFileSync(tgz, tgzBytes);
|
|
581
|
+
const m = pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(tgzBytes), tarballBytes: tgzBytes.length });
|
|
582
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: tgz });
|
|
583
|
+
assert.equal(res.ran, true);
|
|
584
|
+
assert.equal(res.ok, true, res.problems.join('; '));
|
|
585
|
+
assert.deepEqual(res.checks.map((c) => c.name).sort(), ['tarball.sha256', 'tree_sha256']);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
t('verifyInstalledPin: a corrupted installed file → mismatch (ran, not ok)', () => {
|
|
589
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
590
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
591
|
+
writeCoreTree(coreDir, PIN_FILES);
|
|
592
|
+
const m = pinManifestFor(PIN_FILES);
|
|
593
|
+
writeFileSync(join(coreDir, 'a.js'), 'console.log(999) // tampered\n'); // corrupt AFTER pinning
|
|
594
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: null });
|
|
595
|
+
assert.equal(res.ran, true);
|
|
596
|
+
assert.equal(res.ok, false);
|
|
597
|
+
assert.ok(res.problems.some((p) => /content mismatch: a\.js/.test(p)), res.problems.join('; '));
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
t('verifyInstalledPin: a missing installed file → mismatch', () => {
|
|
601
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
602
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
603
|
+
writeCoreTree(coreDir, [PIN_FILES[0]]); // omit sub/b.json
|
|
604
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: pinManifestFor(PIN_FILES), tgzPath: null });
|
|
605
|
+
assert.equal(res.ok, false);
|
|
606
|
+
assert.ok(res.problems.some((p) => /missing installed file: sub\/b\.json/.test(p)), res.problems.join('; '));
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
t('verifyInstalledPin: tarball bytes differ from the pin → mismatch', () => {
|
|
610
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
611
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
612
|
+
writeCoreTree(coreDir, PIN_FILES);
|
|
613
|
+
const tgz = join(dir, 'vendor', 'bongos-core-1.15.0.tgz');
|
|
614
|
+
mkdirSync(dirname(tgz), { recursive: true });
|
|
615
|
+
writeFileSync(tgz, Buffer.from('the-actual-bytes'));
|
|
616
|
+
const m = pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(Buffer.from('DIFFERENT-bytes')), tarballBytes: 9 });
|
|
617
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: tgz });
|
|
618
|
+
assert.equal(res.ok, false);
|
|
619
|
+
assert.ok(res.problems.some((p) => /tarball bytes/.test(p)), res.problems.join('; '));
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
t('verifyInstalledPin: no pin in the reference manifest → ran:false (nothing to check)', () => {
|
|
623
|
+
const res = u.verifyInstalledPin({ coreDir: '/nonexistent', referenceManifest: {}, tgzPath: null });
|
|
624
|
+
assert.equal(res.ran, false);
|
|
625
|
+
assert.equal(res.ok, false);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
t('resolveReferenceManifest: prefers the committed vendored side-car (strong)', () => {
|
|
629
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
630
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
631
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
632
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
|
|
633
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'x');
|
|
634
|
+
const ref = u.resolveReferenceManifest({ instanceDir: dir, coreDir, targetVersion: '1.15.0' });
|
|
635
|
+
assert.equal(ref.source, 'vendored side-car manifest');
|
|
636
|
+
assert.equal(ref.strong, true);
|
|
637
|
+
assert.ok(ref.tgzPath && existsSync(ref.tgzPath));
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
t('resolveReferenceManifest: falls back to the installed inner manifest (not strong)', () => {
|
|
641
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
642
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
643
|
+
mkdirSync(coreDir, { recursive: true });
|
|
644
|
+
writeFileSync(join(coreDir, '.bongos-core.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
|
|
645
|
+
const ref = u.resolveReferenceManifest({ instanceDir: dir, coreDir, targetVersion: '1.15.0' });
|
|
646
|
+
assert.equal(ref.source, 'installed inner manifest');
|
|
647
|
+
assert.equal(ref.strong, false);
|
|
648
|
+
assert.equal(ref.tgzPath, null);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// A spawn shim whose `npm install` writes an installed core tree (+ version) so the
|
|
652
|
+
// real pin-verify step can run against it. `files` is what actually lands on disk.
|
|
653
|
+
function installWritingTree(calls, { dir, version, files }) {
|
|
654
|
+
return (cmd, args) => {
|
|
655
|
+
calls.push(`${cmd} ${(args || []).join(' ')}`.trim());
|
|
656
|
+
if (cmd === 'git' && args[0] === 'status') return { status: 0, stdout: '' };
|
|
657
|
+
if (cmd === 'npm' && args[0] === 'install') {
|
|
658
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
659
|
+
writeCoreTree(coreDir, files);
|
|
660
|
+
writeFileSync(join(coreDir, 'package.json'), JSON.stringify({ name: '@bongos/core', version }) + '\n');
|
|
661
|
+
}
|
|
662
|
+
return { status: 0 };
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
t('runUpgrade: a matching pin passes verification (upgrade proceeds)', async () => {
|
|
667
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
668
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
669
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
|
|
670
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'),
|
|
671
|
+
JSON.stringify(pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(Buffer.from('fake-tgz')), tarballBytes: 8 })));
|
|
672
|
+
const logs = [];
|
|
673
|
+
const res = await u.runUpgrade(
|
|
674
|
+
{ to: '1.15.0', instance: dir },
|
|
675
|
+
{
|
|
676
|
+
log: (m) => logs.push(m), err: () => {},
|
|
677
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
|
|
678
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
assert.equal(res.ok, true, res.error);
|
|
682
|
+
assert.ok(logs.some((m) => /integrity pin verified.*tarball\.sha256.*tree_sha256/.test(m)), logs.join('\n'));
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
t('runUpgrade: a pin MISMATCH after install fails and auto-rolls-back', async () => {
|
|
686
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
687
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
688
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
|
|
689
|
+
// The reference pin is for PIN_FILES, but install writes DIFFERENT content → tree mismatch.
|
|
690
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
|
|
691
|
+
const corrupted = [{ path: 'a.js', content: 'console.log(666)\n' }, { path: 'sub/b.json', content: '{"x":1}\n' }];
|
|
692
|
+
const res = await u.runUpgrade(
|
|
693
|
+
{ to: '1.15.0', instance: dir },
|
|
694
|
+
{
|
|
695
|
+
log: () => {}, err: () => {},
|
|
696
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: corrupted }),
|
|
697
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
assert.equal(res.ok, false);
|
|
701
|
+
assert.ok(/integrity pin MISMATCH/.test(res.error || ''), res.error);
|
|
702
|
+
assert.equal(res.rolledBack, true);
|
|
703
|
+
assert.equal(u.readPinnedCoreVersion(dir), '1.14.1', 'pin reverted to the previous version on rollback');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
t('runUpgrade: --require-pin makes a MISSING reference manifest fatal', async () => {
|
|
707
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
708
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
709
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz'); // tgz present, but NO manifest
|
|
710
|
+
const res = await u.runUpgrade(
|
|
711
|
+
{ to: '1.15.0', instance: dir, requirePin: true },
|
|
712
|
+
{
|
|
713
|
+
log: () => {}, err: () => {},
|
|
714
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
|
|
715
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
assert.equal(res.ok, false);
|
|
719
|
+
assert.ok(/no reference manifest/.test(res.error || ''), res.error);
|
|
720
|
+
assert.equal(res.rolledBack, true, 'unverifiable pin rolls back under --require-pin');
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
t('runUpgrade: --skip-pin-verify bypasses the check (no manifest, still succeeds)', async () => {
|
|
724
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
725
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
726
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
|
|
727
|
+
const res = await u.runUpgrade(
|
|
728
|
+
{ to: '1.15.0', instance: dir, skipPinVerify: true },
|
|
729
|
+
{
|
|
730
|
+
log: () => {}, err: () => {},
|
|
731
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
|
|
732
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
733
|
+
}
|
|
734
|
+
);
|
|
735
|
+
assert.equal(res.ok, true, res.error);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// ---- 8. mandatory post-restart health check (task 1002222 / audit H9, finding F5) ----
|
|
739
|
+
t('runUpgrade: a managed-restart bump WITHOUT --health-url is refused up front (H9 mandatory)', async () => {
|
|
740
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
741
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
742
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
743
|
+
const calls = [];
|
|
744
|
+
const res = await u.runUpgrade(
|
|
745
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' }, // restarts, no --health-url, not opted out
|
|
746
|
+
{ log: () => {}, err: () => {}, run: fakeRun(calls, { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
|
|
747
|
+
);
|
|
748
|
+
assert.equal(res.ok, false);
|
|
749
|
+
assert.match(res.error, /health check is mandatory/);
|
|
750
|
+
assert.equal(u.readPinnedCoreVersion(dir), '1.14.1', 'refused in pre-flight → pin untouched');
|
|
751
|
+
assert.ok(!calls.some((c) => /npm install/.test(c)), 'refused before any install');
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
t('runUpgrade: --no-health-check lets a managed-restart bump proceed without a URL', async () => {
|
|
755
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
756
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
757
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
758
|
+
const logs = [];
|
|
759
|
+
const res = await u.runUpgrade(
|
|
760
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false },
|
|
761
|
+
{ log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
|
|
762
|
+
);
|
|
763
|
+
assert.equal(res.ok, true, res.error);
|
|
764
|
+
assert.ok(logs.some((m) => /health check skipped \(--no-health-check\)/.test(m)), logs.join('\n'));
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
t('runUpgrade: a managed-restart bump WITH a healthy --health-url confirms + succeeds', async () => {
|
|
768
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
769
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
770
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
771
|
+
const logs = [];
|
|
772
|
+
const res = await u.runUpgrade(
|
|
773
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthUrl: 'http://x/healthz' },
|
|
774
|
+
{
|
|
775
|
+
log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }),
|
|
776
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
777
|
+
fetch: async () => ({ ok: true, status: 200 }), sleep: async () => {}, now: () => 0,
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
assert.equal(res.ok, true, res.error);
|
|
781
|
+
assert.equal(res.healthOk, true);
|
|
782
|
+
assert.ok(logs.some((m) => /health http:\/\/x\/healthz → 200/.test(m)), logs.join('\n'));
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
t('runUpgrade: no managed restart + no --health-url proceeds but flags the bump as unconfirmed', async () => {
|
|
786
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
787
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
788
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
789
|
+
const logs = [];
|
|
790
|
+
const res = await u.runUpgrade(
|
|
791
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir }, // no service/restart-cmd, no --health-url
|
|
792
|
+
{ log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
|
|
793
|
+
);
|
|
794
|
+
assert.equal(res.ok, true, res.error);
|
|
795
|
+
assert.equal(res.healthOk, null);
|
|
796
|
+
assert.ok(logs.some((m) => /not confirmed serving/.test(m)), logs.join('\n'));
|
|
797
|
+
});
|
|
798
|
+
|
|
550
799
|
await Promise.all(pending);
|
|
551
800
|
console.log(`\nupgrade: ${passed} passed, ${failed} failed`);
|
|
552
801
|
process.exit(failed ? 1 : 0);
|
package/.gitignore
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Dependencies
|
|
2
|
-
node_modules/
|
|
3
|
-
|
|
4
|
-
# Per-session git worktrees live under here; each is itself a git checkout
|
|
5
|
-
# and must NEVER be committed (would create embedded-repo references).
|
|
6
|
-
.claude/worktrees/
|
|
7
|
-
|
|
8
|
-
# Environment / secrets
|
|
9
|
-
.env
|
|
10
|
-
.env.local
|
|
11
|
-
.env.*.local
|
|
12
|
-
|
|
13
|
-
# Google Chat OAuth credentials — must NEVER be committed.
|
|
14
|
-
# Live on the droplet at /etc/amazonprimea/ instead.
|
|
15
|
-
google-chat-credentials.json
|
|
16
|
-
google-chat-refresh-token.json
|
|
17
|
-
*.credentials.json
|
|
18
|
-
|
|
19
|
-
# Build output
|
|
20
|
-
dist/
|
|
21
|
-
build/
|
|
22
|
-
|
|
23
|
-
# Logs
|
|
24
|
-
*.log
|
|
25
|
-
npm-debug.log*
|
|
26
|
-
|
|
27
|
-
# OS files
|
|
28
|
-
.DS_Store
|
|
29
|
-
|
|
30
|
-
# Pixel art generation artifacts (iterations are huge; keep references + finals only)
|
|
31
|
-
modules/art-pipeline/iterations/*
|
|
32
|
-
# ...but keep the anti-grid FFT baselines tracked (idea 27): they're tiny + the smoke suite
|
|
33
|
-
# (tests/anti_grid.mjs) compares against them. `modules/art-pipeline/iterations/*` (not the bare
|
|
34
|
-
# dir) lets this negation re-include the file — git can't re-include under a fully-excluded dir.
|
|
35
|
-
!modules/art-pipeline/iterations/anti_grid_baselines.json
|
|
36
|
-
modules/art-pipeline/generated/raw/
|
|
37
|
-
modules/art-pipeline/generated/*/raw/
|
|
38
|
-
# Derived from references/ via slice_references.py — easy to regenerate, no need in git
|
|
39
|
-
modules/art-pipeline/template/ref_tiles/
|
|
40
|
-
modules/art-pipeline/template/ref_clusters/
|
|
41
|
-
# Autotile cross-verify snapshot — regenerated on every smoke_test run.
|
|
42
|
-
modules/art-pipeline/pipeline/_autotile_tables.json
|
|
43
|
-
__pycache__/
|
|
44
|
-
*.pyc
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# World snapshots — generated on the droplet by scripts/snapshots/render-world.mjs.
|
|
48
|
-
# Untracked so git reset --hard on deploy doesn't wipe them.
|
|
49
|
-
public-status/snapshots/*.png
|
|
50
|
-
public-status/snapshots/manifest.json
|
|
51
|
-
|
|
52
|
-
# Repo Atlas data — generated on the droplet by scripts/gds/gen-atlas.js on every
|
|
53
|
-
# deploy (ADR 0094). Untracked so git reset --hard doesn't wipe it; the deploy
|
|
54
|
-
# step rewrites it fresh each time, so a committed copy would only go stale.
|
|
55
|
-
# (The hall surface carved to modules/hall-ui/public/ in BV1.R85.)
|
|
56
|
-
modules/hall-ui/public/atlas.json
|
|
57
|
-
|
|
58
|
-
# Whole-file generated navigation docs — untracked since ADR 0110 (task 1926), the
|
|
59
|
-
# same reasoning as the atlas above. docs/repo-map.md (symbol skeleton, ADR 0063) and
|
|
60
|
-
# docs/session-log-index.md (session index, ADR 0062 §8) are 100%-generated and were
|
|
61
|
-
# regenerated + committed on EVERY ship, so two parallel builder branches collided on
|
|
62
|
-
# them constantly. Untracked + regenerated at deploy (scripts/deploy/deploy-prod.sh) and
|
|
63
|
-
# at box-source-fetch, they can never appear in a branch diff → the conflict class is
|
|
64
|
-
# gone at the source. Their generators are dual-duty: the nested CLAUDE.md symbol blocks
|
|
65
|
-
# and the CLAUDE.md §13 snippet stay TRACKED and CI-gated (gen-*.js --check-block).
|
|
66
|
-
docs/repo-map.md
|
|
67
|
-
docs/session-log-index.md
|
|
68
|
-
|
|
69
|
-
# Onboarding diagram RENDERS — untracked since ADR 0110 pt.3 / ADR 0122 (task 2027),
|
|
70
|
-
# the deferred diagram half of ADR 0110. The .svg/.png are regenerated + committed on
|
|
71
|
-
# every render-capable ship, so parallel builder branches collided on them (same class
|
|
72
|
-
# as the nav docs above). Untracked + re-rendered at deploy (scripts/deploy/deploy-prod.sh
|
|
73
|
-
# runs scripts/render-diagrams.sh against the tracked .mmd, using the host's installed
|
|
74
|
-
# Chrome — never puppeteer's own droplet-stalling download) and best-effort at
|
|
75
|
-
# box-source-fetch. The .mmd SOURCES + assertions.json stay TRACKED (git reset --hard
|
|
76
|
-
# restores them; the render regenerates the images from them), so the nightly
|
|
77
|
-
# diagram-drift cron (reads assertions.json) is unaffected.
|
|
78
|
-
docs/onboarding/diagrams/*.svg
|
|
79
|
-
docs/onboarding/diagrams/*.png
|
|
80
|
-
|
|
81
|
-
# Re-hosted #bugs screenshots (task #936). Downloaded from Discord and served
|
|
82
|
-
# by routes/bug-attachments.js. Outside git so a deploy never wipes them.
|
|
83
|
-
var/bug-attachments/
|
|
84
|
-
|
|
85
|
-
# Custom merge-driver scratch files. The regen merge driver
|
|
86
|
-
# (scripts/gds/git-merge-regen.js, installed by scripts/gds/install-git-hooks.js)
|
|
87
|
-
# writes `.merge_file_*` temp files while resolving conflicts. They are pure
|
|
88
|
-
# scratch — never source — but a `git add -A` once staged one onto main
|
|
89
|
-
# (.merge_file_IKo9KW, merge 3e9f1f81). Ignore the pattern so it can't recur.
|
|
90
|
-
.merge_file_*
|
|
91
|
-
scratchpad/
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "requests": [] }
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"achievements": [
|
|
3
|
-
{
|
|
4
|
-
"id": "first-ship",
|
|
5
|
-
"name": "First Ship",
|
|
6
|
-
"description": "Ship your first task.",
|
|
7
|
-
"icon": "📦",
|
|
8
|
-
"credit_bonus": 1,
|
|
9
|
-
"sort_order": 10
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"id": "first-claim",
|
|
13
|
-
"name": "First Claim",
|
|
14
|
-
"description": "Claim your first task.",
|
|
15
|
-
"icon": "🪙",
|
|
16
|
-
"credit_bonus": 1,
|
|
17
|
-
"sort_order": 20
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"id": "first-parallel-claim",
|
|
21
|
-
"name": "First Parallel Claim",
|
|
22
|
-
"description": "Hold two simultaneous claims; ship both successfully.",
|
|
23
|
-
"icon": "⚓",
|
|
24
|
-
"credit_bonus": 2,
|
|
25
|
-
"sort_order": 30
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"id": "streak-3",
|
|
29
|
-
"name": "Streak: 3 Nights",
|
|
30
|
-
"description": "Ship a task in 3 consecutive local-time nights (>=15min; cleanup/refactor now counts).",
|
|
31
|
-
"icon": "🔥",
|
|
32
|
-
"credit_bonus": 2,
|
|
33
|
-
"sort_order": 40
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"id": "streak-7",
|
|
37
|
-
"name": "Streak: 7 Nights",
|
|
38
|
-
"description": "Ship a task in 7 consecutive local-time nights (>=15min; cleanup/refactor now counts).",
|
|
39
|
-
"icon": "🌋",
|
|
40
|
-
"credit_bonus": 5,
|
|
41
|
-
"sort_order": 50
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"id": "art-pipeline-shipper",
|
|
45
|
-
"name": "Art Pipeline Shipper",
|
|
46
|
-
"description": "Ship a task whose touches[] include the art/ pipeline.",
|
|
47
|
-
"icon": "🎨",
|
|
48
|
-
"credit_bonus": 2,
|
|
49
|
-
"sort_order": 60
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"id": "subtractor",
|
|
53
|
-
"name": "Subtractor",
|
|
54
|
-
"description": "Earn the net-negative ship bonus on 3 ships — across your work you have deleted more than you have added.",
|
|
55
|
-
"icon": "🪓",
|
|
56
|
-
"credit_bonus": 2,
|
|
57
|
-
"sort_order": 65
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"id": "gds-shipper",
|
|
61
|
-
"name": "GDS Shipper",
|
|
62
|
-
"description": "Ship a task whose touches[] include src/gds/ or scripts/gds/.",
|
|
63
|
-
"icon": "⚙️",
|
|
64
|
-
"credit_bonus": 2,
|
|
65
|
-
"sort_order": 70
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"id": "breaking-the-100c-bar",
|
|
69
|
-
"name": "Breaking the 100c Bar",
|
|
70
|
-
"description": "Cross 100 total credits.",
|
|
71
|
-
"icon": "💯",
|
|
72
|
-
"credit_bonus": 5,
|
|
73
|
-
"sort_order": 80
|
|
74
|
-
}
|
|
75
|
-
]
|
|
76
|
-
}
|