@bongos/core 1.19.437 → 1.19.439
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 +19 -14
- package/.claude/skills/strand-fix/SKILL.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/lifecycle/ship-assurance.js +12 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/hooks_drain.mjs +58 -0
- package/tests/ship_assurance.mjs +52 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.439",
|
|
6
|
+
"core_contract": "1.19.439",
|
|
7
|
+
"source_commit": "a9bd9c057a92dcd86325488ea8d64d1c5a75d21d",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-30T03:
|
|
9
|
+
"built_at": "2026-08-30T03:36:36.256Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 369,
|
|
13
13
|
"agent_docs_stubbed": 23,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1744,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2136,
|
|
20
|
+
"tree_sha256": "fdf406bc7811d1bced5270e2d1877f3bc5d5a984dfcef33e85fca42755942d6d",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
{
|
|
263
263
|
"path": ".claude/skills/strand-fix/SKILL.md",
|
|
264
264
|
"mode": "0000644",
|
|
265
|
-
"sha256": "
|
|
265
|
+
"sha256": "1444494f4a8f4168c025ef5ac0fd45f88c5217ec3293921cde3ae47b339b3699"
|
|
266
266
|
},
|
|
267
267
|
{
|
|
268
268
|
"path": ".claude/skills/worktree-clean/SKILL.md",
|
|
@@ -1997,7 +1997,7 @@
|
|
|
1997
1997
|
{
|
|
1998
1998
|
"path": "docs/module-api-changelog.md",
|
|
1999
1999
|
"mode": "0000644",
|
|
2000
|
-
"sha256": "
|
|
2000
|
+
"sha256": "f8707c2be03ce4ab61535e578a1f9bc5bbfa39d46d2158e1b6e7500feaa714dd"
|
|
2001
2001
|
},
|
|
2002
2002
|
{
|
|
2003
2003
|
"path": "docs/modules-contract.md",
|
|
@@ -4712,7 +4712,7 @@
|
|
|
4712
4712
|
{
|
|
4713
4713
|
"path": "modules/lifecycle/ship-assurance.js",
|
|
4714
4714
|
"mode": "0000644",
|
|
4715
|
-
"sha256": "
|
|
4715
|
+
"sha256": "9bbbabd157420ffc7ed8c323436a27045da0d7bc7c8bfd7f4c44f300855556b1"
|
|
4716
4716
|
},
|
|
4717
4717
|
{
|
|
4718
4718
|
"path": "modules/lifecycle/ship-card.js",
|
|
@@ -5607,12 +5607,12 @@
|
|
|
5607
5607
|
{
|
|
5608
5608
|
"path": "package-lock.json",
|
|
5609
5609
|
"mode": "0000644",
|
|
5610
|
-
"sha256": "
|
|
5610
|
+
"sha256": "40089194b27619101fe9a2d6ba75e51f24ffd219bf995e79af8f291a032a59f0"
|
|
5611
5611
|
},
|
|
5612
5612
|
{
|
|
5613
5613
|
"path": "package.json",
|
|
5614
5614
|
"mode": "0000644",
|
|
5615
|
-
"sha256": "
|
|
5615
|
+
"sha256": "771d5f5e8c60847e2f0ded55d6d427bb6bda9a0d1e27c58a7918f7b5ceff4eda"
|
|
5616
5616
|
},
|
|
5617
5617
|
{
|
|
5618
5618
|
"path": "public-docs/index.html",
|
|
@@ -7747,7 +7747,7 @@
|
|
|
7747
7747
|
{
|
|
7748
7748
|
"path": "src/module-api.js",
|
|
7749
7749
|
"mode": "0000644",
|
|
7750
|
-
"sha256": "
|
|
7750
|
+
"sha256": "03cd7ebddf88e2be932ca827ec4853b0ce8ae0426e475aa6b84b4e813ebfb06e"
|
|
7751
7751
|
},
|
|
7752
7752
|
{
|
|
7753
7753
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9164,6 +9164,11 @@
|
|
|
9164
9164
|
"mode": "0000644",
|
|
9165
9165
|
"sha256": "ed1e14050ba219a52736c2cd70f4d4333a345fb0018211c85293a995c7f0aac7"
|
|
9166
9166
|
},
|
|
9167
|
+
{
|
|
9168
|
+
"path": "tests/hooks_drain.mjs",
|
|
9169
|
+
"mode": "0000644",
|
|
9170
|
+
"sha256": "d616a075d7e48e2123c08a60b023aad2d2c1cc1bb4a73caa9b6e8189dda30e6f"
|
|
9171
|
+
},
|
|
9167
9172
|
{
|
|
9168
9173
|
"path": "tests/host_topology_skips.mjs",
|
|
9169
9174
|
"mode": "0000644",
|
|
@@ -10267,7 +10272,7 @@
|
|
|
10267
10272
|
{
|
|
10268
10273
|
"path": "tests/ship_assurance.mjs",
|
|
10269
10274
|
"mode": "0000644",
|
|
10270
|
-
"sha256": "
|
|
10275
|
+
"sha256": "63f7b9e24126449b55d8c4fd0c5d8ff4dc11fea2e4eeb7b60d87d66722d5f518"
|
|
10271
10276
|
},
|
|
10272
10277
|
{
|
|
10273
10278
|
"path": "tests/ship_autolinkify.mjs",
|
|
@@ -32,7 +32,7 @@ node scripts/gds/skill-preflight.js strand-fix # exits 1 naming what is MISSIN
|
|
|
32
32
|
gh pr list --search "N" --state all --json number,state,mergeable,url
|
|
33
33
|
```
|
|
34
34
|
2. **Pick the ONE matching case.**
|
|
35
|
-
- **A. A main commit carries `(task N` and the post-merge `unit` + `main-audit` on main are green** (`gh run list --branch main --limit 4`): the land happened; only the proof failed
|
|
35
|
+
- **A. A main commit carries `(task N` and the post-merge `unit` + `main-audit` on main are green** (`gh run list --branch main --limit 4`): the land happened; only the proof failed. The proof is the claim's recorded tip (`claims.published_head_sha`, task 1547) being an ancestor of main — and a `no_branch_no_tip` claim has none (the work rode in on another branch, e.g. a cloud-agent batch, or a `--here` claim). **Record the tip first**: the owner (Archon) sets that column on the task's latest claim to the full 40-char SHA of the main commit — a direct DB write on the box as the DB owner, so it runs under this task's own ledger with the SQL in the ship note (`UPDATE claims SET published_head_sha = '<sha>' WHERE id = (SELECT id FROM claims WHERE task_id = N ORDER BY claimed_at DESC, id DESC LIMIT 1)`). Then the reconciler proves it within ≤5 min, or flip it yourself: `node scripts/gds/api.js POST /api/gds/tasks/N/ship --body '{}'`. Never `no_artifact: true` here — the artifact exists, and the ledger would record a falsehood. Credits land on the flip (ADR 0120). (Before task 1003434 the manual POST refused even with a tip when the claim had no branch; the reconciler path always worked.)
|
|
36
36
|
- **B. A local branch exists with commits ahead of `origin/main` and no PR**: the ship never pushed. From that worktree: `git push -u origin <branch>` then `gh pr create --base main --head <branch> --fill`. Green `unit` → server auto-merge → the reconciler flips it within ~5 min. If it merges but does not flip, do **A**.
|
|
37
37
|
- **C. A PR exists but is `CONFLICTING`**: the branch owner rebases it (`git merge origin/main`, resolve, push) — not this skill, and never `--force`. Report the PR URL and stop.
|
|
38
38
|
- **D. Nothing anywhere** — no branch, no PR, no commit: a codeless confirm. Ask what the task delivered. Work done outside this repo (on the box's instance checkout, a DB-only change captured in the ship note) is legitimately artifact-free → **A** with the reason in the ship body: `--body '{"note":"codeless: <where the work lives>"}'`. If the work was never done, release it instead so it returns to `ready`: `node scripts/gds/release.js N --reason "stranded codeless confirm; nothing delivered"`.
|
|
@@ -1311,5 +1311,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1311
1311
|
landed since 1.19.435 with no explicit bump. run 33288364716. (task 1002620)
|
|
1312
1312
|
1.19.437 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1313
1313
|
landed since 1.19.436 with no explicit bump. run 33289560561. (task 1002620)
|
|
1314
|
+
1.19.438 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1315
|
+
landed since 1.19.437 with no explicit bump. run 33290212472. (task 1002620)
|
|
1316
|
+
1.19.439 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1317
|
+
landed since 1.19.438 with no explicit bump. run 33290633434. (task 1002620)
|
|
1314
1318
|
---------------------------------------------------------------------------
|
|
1315
1319
|
```
|
|
@@ -67,13 +67,22 @@ function normalizeShipAssurance(a) {
|
|
|
67
67
|
//
|
|
68
68
|
// `deps` is injected so this is unit-testable with no network and no DB.
|
|
69
69
|
async function deriveShipAssurance({ taskId, body = {}, db, githubPush, onProbeError }) {
|
|
70
|
+
const report = (e) => { if (onProbeError) onProbeError(e); };
|
|
71
|
+
// The tip is the durable proof (task 1547); the branch/PR status is only a
|
|
72
|
+
// WITNESS for the out-of-band fallback (task 1002615). A claim with no branch
|
|
73
|
+
// makes getPublishStatus throw BAD_BRANCH — that used to abort the whole probe
|
|
74
|
+
// before landProvenForTask ever saw a recorded tip, so a commit provably on main
|
|
75
|
+
// was refused as "not proven" (strands 1003285/1003286, task 1003434). Each step
|
|
76
|
+
// now fails on its own: a status miss degrades to status=null, never to no-proof.
|
|
77
|
+
let publishedSha = null;
|
|
78
|
+
try { publishedSha = await db.getClaimPublishedSha(taskId); } catch (e) { report(e); }
|
|
79
|
+
let status = null;
|
|
80
|
+
try { status = await githubPush.getPublishStatus({ taskId, publishedSha }); } catch (e) { report(e); }
|
|
70
81
|
try {
|
|
71
|
-
const publishedSha = await db.getClaimPublishedSha(taskId);
|
|
72
|
-
const status = await githubPush.getPublishStatus({ taskId, publishedSha });
|
|
73
82
|
const proof = await githubPush.landProvenForTask({ taskId, publishedSha, status });
|
|
74
83
|
if (proof && proof.proven) return { assurance: { kind: 'proof', reason: proof.reason } };
|
|
75
84
|
} catch (e) {
|
|
76
|
-
|
|
85
|
+
report(e);
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
if (body.no_artifact === true) {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.439",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.439",
|
|
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.
|
|
3
|
+
"version": "1.19.439",
|
|
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",
|
package/src/module-api.js
CHANGED
|
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
33
33
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
34
34
|
// the entry to that file. Look for a version's history there, not here.
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
const CORE_VERSION = '1.19.
|
|
36
|
+
const CORE_VERSION = '1.19.439'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
37
37
|
|
|
38
38
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
39
39
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// tests/hooks_drain.mjs — the hooks that talk to the API must exit by DRAINING, never
|
|
2
|
+
// process.exit() (task 1003433; task 1003432 for the Stop hook). On Windows, exiting
|
|
3
|
+
// while a pooled socket is mid-close aborts Node with libuv's UV_HANDLE_CLOSING
|
|
4
|
+
// assertion — a red "hook error" line on every prompt. Each hook is spawned the way
|
|
5
|
+
// the harness spawns it; the contract is: exits on its own, exit 0, no abort on stderr.
|
|
6
|
+
//
|
|
7
|
+
// Offline by construction: no session token in the env and a throwaway HOME, so every
|
|
8
|
+
// hook takes its "not signed in" no-op path — the exit mechanics are what's under test.
|
|
9
|
+
//
|
|
10
|
+
// .claude/hooks/ is instance-materialized and package-excluded (PROTECTED_TOPOLOGY_EXCLUDE
|
|
11
|
+
// in scripts/gds/publish-manifest.js), so a clean core checkout has no hooks: skip cleanly.
|
|
12
|
+
//
|
|
13
|
+
// Run: node tests/hooks_drain.mjs
|
|
14
|
+
|
|
15
|
+
import { strict as assert } from 'node:assert';
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
const HOOKS = ['recall-hints', 'rank-change-greeting', 'conductor'].map((n) => path.join(ROOT, '.claude', 'hooks', `${n}.js`));
|
|
24
|
+
|
|
25
|
+
if (!HOOKS.every((p) => fs.existsSync(p))) {
|
|
26
|
+
console.log('hooks_drain: SKIP — .claude/hooks/ is not materialized in this checkout (package-excluded).');
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let passed = 0;
|
|
31
|
+
let failed = 0;
|
|
32
|
+
function t(name, fn) {
|
|
33
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
34
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'hooks-drain-'));
|
|
38
|
+
const transcript = path.join(HOME, 't.jsonl');
|
|
39
|
+
fs.writeFileSync(transcript, JSON.stringify({ type: 'assistant', message: { content: [{ type: 'text', text: 'hello' }] } }) + '\n');
|
|
40
|
+
const payload = JSON.stringify({ session_id: 's1', transcript_path: transcript, cwd: ROOT, hook_event_name: 'UserPromptSubmit', prompt: 'status?' });
|
|
41
|
+
|
|
42
|
+
const env = { ...process.env, HOME, USERPROFILE: HOME, CLAUDE_PROJECT_DIR: ROOT };
|
|
43
|
+
for (const k of Object.keys(env)) if (/_API_BASE$|^OTB_|^CLOUDBONGOS_|^GDS_|^PMS_/.test(k)) delete env[k];
|
|
44
|
+
|
|
45
|
+
for (const hook of HOOKS) {
|
|
46
|
+
t(`${path.basename(hook)} exits on its own with 0 and no libuv abort (x3)`, () => {
|
|
47
|
+
for (let i = 0; i < 3; i++) {
|
|
48
|
+
const r = spawnSync(process.execPath, [hook], { input: payload, encoding: 'utf8', timeout: 15000, env });
|
|
49
|
+
assert.equal(r.signal, null, `run ${i}: killed by ${r.signal} — did not drain`);
|
|
50
|
+
assert.equal(r.status, 0, `run ${i}: exit ${r.status}\n${r.stderr}`);
|
|
51
|
+
assert.ok(!/Assertion failed/.test(r.stderr || ''), `run ${i}: libuv abort on stderr:\n${r.stderr}`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fs.rmSync(HOME, { recursive: true, force: true });
|
|
57
|
+
console.log(`\nhooks_drain: ${passed} passed, ${failed} failed`);
|
|
58
|
+
process.exit(failed ? 1 : 0);
|
package/tests/ship_assurance.mjs
CHANGED
|
@@ -186,5 +186,57 @@ await t('the CLI carries the declaration through its deliberate-empty path', ()
|
|
|
186
186
|
'and the deliberate-empty land sites must mark themselves as such');
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
// task 1003434 — a recorded tip proves the land even when the claim has no branch.
|
|
190
|
+
// Strands 1003285/1003286: commits on main, no branch on the claim. Once the tip was
|
|
191
|
+
// recorded, POST /ship still refused, because getPublishStatus threw BAD_BRANCH before
|
|
192
|
+
// landProvenForTask ever saw the tip. The status probe is a witness, not a
|
|
193
|
+
// prerequisite: its failure must degrade to status=null, never discard the proof.
|
|
194
|
+
const { deriveShipAssurance } = require(path.join(ROOT, 'modules/lifecycle/ship-assurance.js'));
|
|
195
|
+
const TIP = 'a'.repeat(40);
|
|
196
|
+
console.log('\nthe tip proof does not depend on the branch/PR probe');
|
|
197
|
+
await t('tip in main + getPublishStatus throwing (no branch on the claim) → proof', async () => {
|
|
198
|
+
const seen = [];
|
|
199
|
+
const r = await deriveShipAssurance({
|
|
200
|
+
taskId: 1003285,
|
|
201
|
+
body: {},
|
|
202
|
+
db: { getClaimPublishedSha: async () => TIP },
|
|
203
|
+
githubPush: {
|
|
204
|
+
getPublishStatus: async () => { const e = new Error('BAD_BRANCH'); e.code = 'BAD_BRANCH'; throw e; },
|
|
205
|
+
landProvenForTask: async ({ publishedSha, status }) => {
|
|
206
|
+
seen.push({ publishedSha, status });
|
|
207
|
+
return publishedSha === TIP ? { proven: true, reason: 'published tip in main' } : { proven: false };
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
onProbeError: (e) => seen.push({ probeError: e.code }),
|
|
211
|
+
});
|
|
212
|
+
assert.equal(seen.some((s) => s.publishedSha === TIP && s.status === null), true, 'the proof ran with status=null');
|
|
213
|
+
assert.equal(seen.some((s) => s.probeError === 'BAD_BRANCH'), true, 'the probe failure was still reported');
|
|
214
|
+
assert.equal(r.assurance && r.assurance.kind, 'proof');
|
|
215
|
+
});
|
|
216
|
+
await t('no tip + getPublishStatus throwing → still the 409 refusal (nothing to prove)', async () => {
|
|
217
|
+
const r = await deriveShipAssurance({
|
|
218
|
+
taskId: 1003253,
|
|
219
|
+
body: {},
|
|
220
|
+
db: { getClaimPublishedSha: async () => null },
|
|
221
|
+
githubPush: {
|
|
222
|
+
getPublishStatus: async () => { throw new Error('BAD_BRANCH'); },
|
|
223
|
+
landProvenForTask: async () => ({ proven: false, reason: 'no tip, no PR' }),
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
assert.equal(r.refusal && r.refusal.code, 'ship_land_not_proven');
|
|
227
|
+
});
|
|
228
|
+
await t('a proof failure (compare API down) still falls through to the refusal, not a throw', async () => {
|
|
229
|
+
const r = await deriveShipAssurance({
|
|
230
|
+
taskId: 1,
|
|
231
|
+
body: {},
|
|
232
|
+
db: { getClaimPublishedSha: async () => TIP },
|
|
233
|
+
githubPush: {
|
|
234
|
+
getPublishStatus: async () => null,
|
|
235
|
+
landProvenForTask: async () => { throw new Error('STATUS_FETCH_FAILED'); },
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
assert.equal(r.refusal && r.refusal.code, 'ship_land_not_proven');
|
|
239
|
+
});
|
|
240
|
+
|
|
189
241
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
190
242
|
process.exit(failed ? 1 : 0);
|