@bongos/core 1.19.674 → 1.19.676
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 +32 -27
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +61 -3
- package/docs/api-reference.md +3 -2
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/core-release-pipeline.md +12 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/discord/discord-channels.js +107 -0
- package/modules/discord/routes/discord.js +26 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/discord-channels.js +14 -3
- package/scripts/gds/go-live.js +118 -25
- package/scripts/gds/upgrade.js +28 -4
- package/src/module-api.js +1 -1
- package/tests/discord_channels_snapshot.mjs +198 -0
- package/tests/go_live.mjs +174 -2
- package/tests/upgrade_persist_pin.mjs +61 -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.676",
|
|
6
|
+
"core_contract": "1.19.676",
|
|
7
|
+
"source_commit": "9aad4d0005991e8ba38f4443b4e5611a13aa3af8",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-11T20:44:28.469Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 474,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2123,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2621,
|
|
20
|
+
"tree_sha256": "b556a6e9644d99b65ffdf9ac64384eee16b20ecfb7e31ed60c875d76297310f3",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -367,12 +367,12 @@
|
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/README.md",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "b15290057c38e4b80db4136d7fdd32549718e31f5975484e77098c66050c4366"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/bongos-client.global.js",
|
|
374
374
|
"mode": "0000644",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "bdf715d3196df99d675570748f7ac054522046cbe89d78b84c27eef89a52d18f"
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"path": "clients/bongos-client/examples/hello-world.mjs",
|
|
@@ -382,17 +382,17 @@
|
|
|
382
382
|
{
|
|
383
383
|
"path": "clients/bongos-client/index.cjs",
|
|
384
384
|
"mode": "0000644",
|
|
385
|
-
"sha256": "
|
|
385
|
+
"sha256": "2fbbab84a89021945a707fd21d72acbad7f03b77c5fb6eb39abce44a64d4cc1b"
|
|
386
386
|
},
|
|
387
387
|
{
|
|
388
388
|
"path": "clients/bongos-client/index.d.ts",
|
|
389
389
|
"mode": "0000644",
|
|
390
|
-
"sha256": "
|
|
390
|
+
"sha256": "22523f2527d8849445122c5a03332d777a63d849a5806e33d387cbd83c54fb2f"
|
|
391
391
|
},
|
|
392
392
|
{
|
|
393
393
|
"path": "clients/bongos-client/index.mjs",
|
|
394
394
|
"mode": "0000644",
|
|
395
|
-
"sha256": "
|
|
395
|
+
"sha256": "eeb72bda7a89900279647d6d999d2a920a986bf2f4ed3a63782cd4e36b1b3dfc"
|
|
396
396
|
},
|
|
397
397
|
{
|
|
398
398
|
"path": "clients/bongos-client/package.json",
|
|
@@ -1912,12 +1912,12 @@
|
|
|
1912
1912
|
{
|
|
1913
1913
|
"path": "docs/api-reference.md",
|
|
1914
1914
|
"mode": "0000644",
|
|
1915
|
-
"sha256": "
|
|
1915
|
+
"sha256": "6b681ad5de20a67efa1d6d024d5cb5dbe5313add4ec51d94a30a0025fa567ba2"
|
|
1916
1916
|
},
|
|
1917
1917
|
{
|
|
1918
1918
|
"path": "docs/api/openapi.json",
|
|
1919
1919
|
"mode": "0000644",
|
|
1920
|
-
"sha256": "
|
|
1920
|
+
"sha256": "301cf1db00b9f18b84cb765bbaa359329a665b1ef754dff4c21c1550ec60cae5"
|
|
1921
1921
|
},
|
|
1922
1922
|
{
|
|
1923
1923
|
"path": "docs/architecture.md",
|
|
@@ -2792,7 +2792,7 @@
|
|
|
2792
2792
|
{
|
|
2793
2793
|
"path": "docs/module-api-changelog.md",
|
|
2794
2794
|
"mode": "0000644",
|
|
2795
|
-
"sha256": "
|
|
2795
|
+
"sha256": "b8786c58575f93eb93661ee1d7e0745f0993f5666e5237ca073177df2d3ee9f4"
|
|
2796
2796
|
},
|
|
2797
2797
|
{
|
|
2798
2798
|
"path": "docs/modules-contract.md",
|
|
@@ -2912,7 +2912,7 @@
|
|
|
2912
2912
|
{
|
|
2913
2913
|
"path": "docs/recipes/core-release-pipeline.md",
|
|
2914
2914
|
"mode": "0000644",
|
|
2915
|
-
"sha256": "
|
|
2915
|
+
"sha256": "86d266be2c1833484225886c1ed961b8f19d511e84226032dc65c9ccf18f5550"
|
|
2916
2916
|
},
|
|
2917
2917
|
{
|
|
2918
2918
|
"path": "docs/recipes/devbox-app-release.md",
|
|
@@ -4242,7 +4242,7 @@
|
|
|
4242
4242
|
{
|
|
4243
4243
|
"path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
|
|
4244
4244
|
"mode": "0000644",
|
|
4245
|
-
"sha256": "
|
|
4245
|
+
"sha256": "2fbbab84a89021945a707fd21d72acbad7f03b77c5fb6eb39abce44a64d4cc1b"
|
|
4246
4246
|
},
|
|
4247
4247
|
{
|
|
4248
4248
|
"path": "modules/dev-box/app/test/unit.mjs",
|
|
@@ -4332,7 +4332,7 @@
|
|
|
4332
4332
|
{
|
|
4333
4333
|
"path": "modules/discord/discord-channels.js",
|
|
4334
4334
|
"mode": "0000644",
|
|
4335
|
-
"sha256": "
|
|
4335
|
+
"sha256": "aeeb86c5d61b4a06ae5429fb69bb4720d54f4782ead5d23cdce640344a568246"
|
|
4336
4336
|
},
|
|
4337
4337
|
{
|
|
4338
4338
|
"path": "modules/discord/discord-inbound.js",
|
|
@@ -4382,7 +4382,7 @@
|
|
|
4382
4382
|
{
|
|
4383
4383
|
"path": "modules/discord/routes/discord.js",
|
|
4384
4384
|
"mode": "0000644",
|
|
4385
|
-
"sha256": "
|
|
4385
|
+
"sha256": "2229a563e11bd05f0eb047f0afc0e1d66eaddb6a7fa2fb07c429e162684246f0"
|
|
4386
4386
|
},
|
|
4387
4387
|
{
|
|
4388
4388
|
"path": "modules/discord/ship-broadcast.js",
|
|
@@ -7767,12 +7767,12 @@
|
|
|
7767
7767
|
{
|
|
7768
7768
|
"path": "package-lock.json",
|
|
7769
7769
|
"mode": "0000644",
|
|
7770
|
-
"sha256": "
|
|
7770
|
+
"sha256": "3b0ebf2f1fb870085a40531295fb3c5989be5ab399e2493e43a1e3e844b0633c"
|
|
7771
7771
|
},
|
|
7772
7772
|
{
|
|
7773
7773
|
"path": "package.json",
|
|
7774
7774
|
"mode": "0000644",
|
|
7775
|
-
"sha256": "
|
|
7775
|
+
"sha256": "b4c1e3c5189ac7220af056624bdc0476a0d751c00aa76aba2e84416128280bc7"
|
|
7776
7776
|
},
|
|
7777
7777
|
{
|
|
7778
7778
|
"path": "public-docs/index.html",
|
|
@@ -8152,7 +8152,7 @@
|
|
|
8152
8152
|
{
|
|
8153
8153
|
"path": "scripts/gds/discord-channels.js",
|
|
8154
8154
|
"mode": "0000644",
|
|
8155
|
-
"sha256": "
|
|
8155
|
+
"sha256": "e142b2d64e919adc4781ea6a55796113ebde3a3645bd4c4ed113ff9f13cd7f6a"
|
|
8156
8156
|
},
|
|
8157
8157
|
{
|
|
8158
8158
|
"path": "scripts/gds/do-api.js",
|
|
@@ -8327,7 +8327,7 @@
|
|
|
8327
8327
|
{
|
|
8328
8328
|
"path": "scripts/gds/go-live.js",
|
|
8329
8329
|
"mode": "0000644",
|
|
8330
|
-
"sha256": "
|
|
8330
|
+
"sha256": "d1383b714ba0d8e4d0f5ceb097e45c1ed1af09caf648f7a43a6c6431c38fb422"
|
|
8331
8331
|
},
|
|
8332
8332
|
{
|
|
8333
8333
|
"path": "scripts/gds/goal-names.js",
|
|
@@ -9102,7 +9102,7 @@
|
|
|
9102
9102
|
{
|
|
9103
9103
|
"path": "scripts/gds/upgrade.js",
|
|
9104
9104
|
"mode": "0000644",
|
|
9105
|
-
"sha256": "
|
|
9105
|
+
"sha256": "7a173ecf47b25007bba28f68787893352f429a52b34b7f7b4eae0d3791a5cdcf"
|
|
9106
9106
|
},
|
|
9107
9107
|
{
|
|
9108
9108
|
"path": "scripts/gds/validate-design.js",
|
|
@@ -9532,7 +9532,7 @@
|
|
|
9532
9532
|
{
|
|
9533
9533
|
"path": "src/module-api.js",
|
|
9534
9534
|
"mode": "0000644",
|
|
9535
|
-
"sha256": "
|
|
9535
|
+
"sha256": "406c218b56b011b4560887f2ae4e13f20e227fa4404e6763649e2eac2b0f5292"
|
|
9536
9536
|
},
|
|
9537
9537
|
{
|
|
9538
9538
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10439,6 +10439,11 @@
|
|
|
10439
10439
|
"mode": "0000644",
|
|
10440
10440
|
"sha256": "981d34c2fb5ecb6e9b9f38ece947c6c7a400dceadf8bbcf09546c8b3a92eb899"
|
|
10441
10441
|
},
|
|
10442
|
+
{
|
|
10443
|
+
"path": "tests/discord_channels_snapshot.mjs",
|
|
10444
|
+
"mode": "0000644",
|
|
10445
|
+
"sha256": "d4e43501ac29ea920c0c064afe601bd9fc24f87808f10986a51d981b3111946b"
|
|
10446
|
+
},
|
|
10442
10447
|
{
|
|
10443
10448
|
"path": "tests/discord_foundation.mjs",
|
|
10444
10449
|
"mode": "0000644",
|
|
@@ -10662,7 +10667,7 @@
|
|
|
10662
10667
|
{
|
|
10663
10668
|
"path": "tests/go_live.mjs",
|
|
10664
10669
|
"mode": "0000644",
|
|
10665
|
-
"sha256": "
|
|
10670
|
+
"sha256": "7528da9c6cd81d9876f0ffae0bf836844cb726a9bbb24cda44f0675d6f6ac0ea"
|
|
10666
10671
|
},
|
|
10667
10672
|
{
|
|
10668
10673
|
"path": "tests/goal_abuse_matrix.mjs",
|
|
@@ -12977,7 +12982,7 @@
|
|
|
12977
12982
|
{
|
|
12978
12983
|
"path": "tests/upgrade_persist_pin.mjs",
|
|
12979
12984
|
"mode": "0000644",
|
|
12980
|
-
"sha256": "
|
|
12985
|
+
"sha256": "c0a96fbdc6a9f851925babfb0857c9a168374c4165c84884f5c4c56c7177dd58"
|
|
12981
12986
|
},
|
|
12982
12987
|
{
|
|
12983
12988
|
"path": "tests/verify_task_guard.mjs",
|
|
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
|
|
|
5
5
|
by hand; it regenerates when the spec changes, so it can never drift from the routes.
|
|
6
6
|
|
|
7
7
|
- API version: **v1** (served at `/api/bongos/v1`)
|
|
8
|
-
-
|
|
8
|
+
- 363 operations across 55 resource groups
|
|
9
9
|
|
|
10
10
|
## Use it from your project
|
|
11
11
|
|
|
@@ -313,6 +313,8 @@ function createClient(opts = {}) {
|
|
|
313
313
|
getDiscordChannelsPlan: (args) => request("GET", "/discord/channels/plan", { hasBody: false }, args),
|
|
314
314
|
// POST /discord/channels/reconcile — rank: metic+archon — POST /discord/channels/reconcile
|
|
315
315
|
postDiscordChannelsReconcile: (args) => request("POST", "/discord/channels/reconcile", { hasBody: true }, args),
|
|
316
|
+
// GET /discord/channels/snapshot — rank: metic+archon — GET /discord/channels/snapshot
|
|
317
|
+
getDiscordChannelsSnapshot: (args) => request("GET", "/discord/channels/snapshot", { hasBody: false }, args),
|
|
316
318
|
},
|
|
317
319
|
"doneWhen": {
|
|
318
320
|
// PATCH /done-when/{criterionId} — rank: metic+archon — PATCH /done-when/:criterionId
|
|
@@ -312,6 +312,8 @@ function createClient(opts = {}) {
|
|
|
312
312
|
getDiscordChannelsPlan: (args) => request("GET", "/discord/channels/plan", { hasBody: false }, args),
|
|
313
313
|
// POST /discord/channels/reconcile — rank: metic+archon — POST /discord/channels/reconcile
|
|
314
314
|
postDiscordChannelsReconcile: (args) => request("POST", "/discord/channels/reconcile", { hasBody: true }, args),
|
|
315
|
+
// GET /discord/channels/snapshot — rank: metic+archon — GET /discord/channels/snapshot
|
|
316
|
+
getDiscordChannelsSnapshot: (args) => request("GET", "/discord/channels/snapshot", { hasBody: false }, args),
|
|
315
317
|
},
|
|
316
318
|
"doneWhen": {
|
|
317
319
|
// PATCH /done-when/{criterionId} — rank: metic+archon — PATCH /done-when/:criterionId
|
|
@@ -45,6 +45,7 @@ export interface GetConnectionsDegreeHandleResponse { handle: unknown; degree: u
|
|
|
45
45
|
export interface GetConnectionsRequestsResponse { incoming: unknown; outgoing: unknown; incoming_count: unknown; outgoing_count: unknown }
|
|
46
46
|
export interface GetConnectionsResponse { connections: unknown; count: unknown }
|
|
47
47
|
export interface GetDiscordChannelsPlanResponse { ok: boolean; guild_id: unknown; op_count: unknown; ops: unknown; warnings: unknown }
|
|
48
|
+
export interface GetDiscordChannelsSnapshotResponse { ok: boolean; guild_id: unknown; config: unknown; warnings: unknown }
|
|
48
49
|
export interface GetDoneWhenPendingReviewResponse { criteria: unknown; count: unknown }
|
|
49
50
|
export interface GetGithubRepoResponse { ok: boolean; repo: unknown; private_deployable: boolean }
|
|
50
51
|
export interface GetGithubReposResponse { repos: unknown }
|
|
@@ -647,6 +648,8 @@ export interface BongosClient {
|
|
|
647
648
|
getDiscordChannelsPlan(args?: RequestArgs): Promise<GetDiscordChannelsPlanResponse>;
|
|
648
649
|
/** POST /discord/channels/reconcile — rank: metic+archon */
|
|
649
650
|
postDiscordChannelsReconcile(args?: RequestArgs): Promise<PostDiscordChannelsReconcileResponse>;
|
|
651
|
+
/** GET /discord/channels/snapshot — rank: metic+archon */
|
|
652
|
+
getDiscordChannelsSnapshot(args?: RequestArgs): Promise<GetDiscordChannelsSnapshotResponse>;
|
|
650
653
|
};
|
|
651
654
|
"doneWhen": {
|
|
652
655
|
/** PATCH /done-when/{criterionId} — rank: metic+archon */
|
|
@@ -309,6 +309,8 @@ export function createClient(opts = {}) {
|
|
|
309
309
|
getDiscordChannelsPlan: (args) => request("GET", "/discord/channels/plan", { hasBody: false }, args),
|
|
310
310
|
// POST /discord/channels/reconcile — rank: metic+archon — POST /discord/channels/reconcile
|
|
311
311
|
postDiscordChannelsReconcile: (args) => request("POST", "/discord/channels/reconcile", { hasBody: true }, args),
|
|
312
|
+
// GET /discord/channels/snapshot — rank: metic+archon — GET /discord/channels/snapshot
|
|
313
|
+
getDiscordChannelsSnapshot: (args) => request("GET", "/discord/channels/snapshot", { hasBody: false }, args),
|
|
312
314
|
},
|
|
313
315
|
"doneWhen": {
|
|
314
316
|
// PATCH /done-when/{criterionId} — rank: metic+archon — PATCH /done-when/:criterionId
|
package/docs/api/openapi.json
CHANGED
|
@@ -4338,6 +4338,47 @@
|
|
|
4338
4338
|
]
|
|
4339
4339
|
}
|
|
4340
4340
|
},
|
|
4341
|
+
"/discord/channels/snapshot": {
|
|
4342
|
+
"get": {
|
|
4343
|
+
"operationId": "get_discord_channels_snapshot",
|
|
4344
|
+
"tags": [
|
|
4345
|
+
"discord"
|
|
4346
|
+
],
|
|
4347
|
+
"summary": "GET /discord/channels/snapshot",
|
|
4348
|
+
"description": "GET /api/bongos/discord/channels/snapshot — what docs/discord/channels.json WOULD say if it described the guild exactly as it is right now (task 1003510). Read-only in both directions: it writes nothing to Discord and nothing to the repo — the operator reviews the output and commits it, so the layout stays Archon-gated by merge, which is the property ADR 0037 §1 rests on. 503 when the bot is disabled / not connected to a guild. rank: archon — channel administration command.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `discord.channels.manage` (all required).",
|
|
4349
|
+
"x-rank": "metic+archon",
|
|
4350
|
+
"x-source": "modules/discord/routes/discord.js",
|
|
4351
|
+
"x-permissions": [
|
|
4352
|
+
"discord.channels.manage"
|
|
4353
|
+
],
|
|
4354
|
+
"responses": {
|
|
4355
|
+
"200": {
|
|
4356
|
+
"description": "Success.",
|
|
4357
|
+
"content": {
|
|
4358
|
+
"application/json": {
|
|
4359
|
+
"schema": {
|
|
4360
|
+
"$ref": "#/components/schemas/GetDiscordChannelsSnapshotResponse"
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
},
|
|
4365
|
+
"400": {
|
|
4366
|
+
"$ref": "#/components/responses/BadRequest"
|
|
4367
|
+
},
|
|
4368
|
+
"401": {
|
|
4369
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
4370
|
+
},
|
|
4371
|
+
"403": {
|
|
4372
|
+
"$ref": "#/components/responses/Forbidden"
|
|
4373
|
+
}
|
|
4374
|
+
},
|
|
4375
|
+
"security": [
|
|
4376
|
+
{
|
|
4377
|
+
"builderSession": []
|
|
4378
|
+
}
|
|
4379
|
+
]
|
|
4380
|
+
}
|
|
4381
|
+
},
|
|
4341
4382
|
"/done-when/pending-review": {
|
|
4342
4383
|
"get": {
|
|
4343
4384
|
"operationId": "get_done_when_pending_review",
|
|
@@ -17101,6 +17142,23 @@
|
|
|
17101
17142
|
"warnings"
|
|
17102
17143
|
]
|
|
17103
17144
|
},
|
|
17145
|
+
"GetDiscordChannelsSnapshotResponse": {
|
|
17146
|
+
"type": "object",
|
|
17147
|
+
"properties": {
|
|
17148
|
+
"ok": {
|
|
17149
|
+
"type": "boolean"
|
|
17150
|
+
},
|
|
17151
|
+
"guild_id": {},
|
|
17152
|
+
"config": {},
|
|
17153
|
+
"warnings": {}
|
|
17154
|
+
},
|
|
17155
|
+
"required": [
|
|
17156
|
+
"ok",
|
|
17157
|
+
"guild_id",
|
|
17158
|
+
"config",
|
|
17159
|
+
"warnings"
|
|
17160
|
+
]
|
|
17161
|
+
},
|
|
17104
17162
|
"GetDoneWhenPendingReviewResponse": {
|
|
17105
17163
|
"type": "object",
|
|
17106
17164
|
"properties": {
|
|
@@ -22955,9 +23013,9 @@
|
|
|
22955
23013
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
22956
23014
|
}
|
|
22957
23015
|
},
|
|
22958
|
-
"x-endpoint-count":
|
|
22959
|
-
"x-schema-count":
|
|
23016
|
+
"x-endpoint-count": 363,
|
|
23017
|
+
"x-schema-count": 402,
|
|
22960
23018
|
"x-undocumented-bodies": 9,
|
|
22961
|
-
"x-response-schemas":
|
|
23019
|
+
"x-response-schemas": 271,
|
|
22962
23020
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
22963
23021
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Bongos API reference
|
|
4
4
|
|
|
5
|
-
> **Generated from the live route files** — the route file is authoritative.
|
|
5
|
+
> **Generated from the live route files** — the route file is authoritative. 363 endpoints across 58 route files.
|
|
6
6
|
> Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
|
|
7
7
|
|
|
8
8
|
Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
|
|
@@ -191,12 +191,13 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
191
191
|
| POST | `/api/bongos/dependencies` | `archon` | — | POST /dependencies — create an edge "from is blocked until to is done". |
|
|
192
192
|
| DELETE | `/api/bongos/dependencies` | `archon` | — | DELETE /dependencies — remove an edge (body carries the 4-tuple). |
|
|
193
193
|
|
|
194
|
-
## `discord` (
|
|
194
|
+
## `discord` (3)
|
|
195
195
|
|
|
196
196
|
| Method | Path | Rank | Body | Description |
|
|
197
197
|
|---|---|---|---|---|
|
|
198
198
|
| GET | `/api/bongos/discord/channels/plan` | `metic+archon` | — | GET /api/bongos/discord/channels/plan — dry run. |
|
|
199
199
|
| POST | `/api/bongos/discord/channels/reconcile` | `metic+archon` | — | POST /api/bongos/discord/channels/reconcile — apply the config to Discord now. |
|
|
200
|
+
| GET | `/api/bongos/discord/channels/snapshot` | `metic+archon` | — | GET /api/bongos/discord/channels/snapshot — what docs/discord/channels.json WOULD say if it described the guild exactly as it is right no… |
|
|
200
201
|
|
|
201
202
|
## `done-when` (4)
|
|
202
203
|
|
|
@@ -1807,5 +1807,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1807
1807
|
landed since 1.19.672 with no explicit bump. run 34632328471. (task 1002620)
|
|
1808
1808
|
1.19.674 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1809
1809
|
landed since 1.19.673 with no explicit bump. run 34634515595. (task 1002620)
|
|
1810
|
+
1.19.675 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1811
|
+
landed since 1.19.674 with no explicit bump. run 34639371461. (task 1002620)
|
|
1812
|
+
1.19.676 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1813
|
+
landed since 1.19.675 with no explicit bump. run 34645751352. (task 1002620)
|
|
1810
1814
|
---------------------------------------------------------------------------
|
|
1811
1815
|
```
|
|
@@ -168,6 +168,18 @@ Host topology (ssh alias, box paths, service names, health URL) lives in a host-
|
|
|
168
168
|
`config/go-live.json`, never in the core — run `node scripts/gds/go-live.js --help` for the
|
|
169
169
|
schema. A core that named one instance's box would not be a core.
|
|
170
170
|
|
|
171
|
+
**Upgrading a provisioned co-tenant** (task 1003521). A co-tenant under `/srv/<host>/<slug>` is
|
|
172
|
+
the same engine with two fields different, because two of this instance's assumptions do not hold
|
|
173
|
+
there: it has no pull-deploy timer to stop (only its `<slug>-db-backup.timer`), and its repo of
|
|
174
|
+
record is the **customer's** own GitHub repo, so pushing the pin there is both wrong and
|
|
175
|
+
uncredentialed. So omit `deployTimer` — the stop and the restart are skipped, and there is no
|
|
176
|
+
`reset --hard` to race — and set `"pinMode": "local-commit"`, which commits the pin without
|
|
177
|
+
pushing it. (`"leave-dirty"` leaves the bump in the working tree; `"push"` is the default and is
|
|
178
|
+
what the platform instance needs.) Everything else — the registry proof, `--verify-marker`, the
|
|
179
|
+
`pg_dump`, the health-gated upgrade with auto-rollback, the box-side version read-back — is
|
|
180
|
+
unchanged. The co-tenant's own scaffold gap is separate and still open:
|
|
181
|
+
[#1003520](https://cloudbongos.com/builders#/task/1003520).
|
|
182
|
+
|
|
171
183
|
### Step 1 — pick the version (your machine, main checkout)
|
|
172
184
|
|
|
173
185
|
```bash
|
|
@@ -312,6 +312,8 @@ function createClient(opts = {}) {
|
|
|
312
312
|
getDiscordChannelsPlan: (args) => request("GET", "/discord/channels/plan", { hasBody: false }, args),
|
|
313
313
|
// POST /discord/channels/reconcile — rank: metic+archon — POST /discord/channels/reconcile
|
|
314
314
|
postDiscordChannelsReconcile: (args) => request("POST", "/discord/channels/reconcile", { hasBody: true }, args),
|
|
315
|
+
// GET /discord/channels/snapshot — rank: metic+archon — GET /discord/channels/snapshot
|
|
316
|
+
getDiscordChannelsSnapshot: (args) => request("GET", "/discord/channels/snapshot", { hasBody: false }, args),
|
|
315
317
|
},
|
|
316
318
|
"doneWhen": {
|
|
317
319
|
// PATCH /done-when/{criterionId} — rank: metic+archon — PATCH /done-when/:criterionId
|
|
@@ -178,6 +178,98 @@ function viewOverwritesEqual(a, b, ctx) {
|
|
|
178
178
|
// categories: [{ id, name }],
|
|
179
179
|
// channels: [{ id, name, type, parentId, topic, overwrites:[{id,type,allow,deny}] }]
|
|
180
180
|
// }
|
|
181
|
+
// viewFromOverwrites — the INVERSE of resolveViewRoles + mergeViewOverwrites:
|
|
182
|
+
// read a channel's real permission overwrites and say which `view` value would
|
|
183
|
+
// produce them. Pure (task 1003510).
|
|
184
|
+
//
|
|
185
|
+
// `view: R` grants R and every rank ABOVE it, so the answer is the LOWEST rank
|
|
186
|
+
// on the ladder holding an explicit VIEW allow. @everyone allowed outranks all
|
|
187
|
+
// of it and means "everyone".
|
|
188
|
+
//
|
|
189
|
+
// Returns null for "cannot tell" — no @everyone overwrite AND no rank role
|
|
190
|
+
// allowed, which is a channel the bot has never touched and which simply
|
|
191
|
+
// inherits its category. Null is the honest answer there, and the caller emits
|
|
192
|
+
// no `view` key so the channel inherits in the config exactly as it does in the
|
|
193
|
+
// guild. Do NOT collapse null to 'everyone': that would write a claim the guild
|
|
194
|
+
// never made, and a later reconcile would act on it.
|
|
195
|
+
function viewFromOverwrites(overwrites, ctx) {
|
|
196
|
+
const { everyoneId, roleMap = {} } = ctx || {};
|
|
197
|
+
const has = (o, field) => (BigInt((o && o[field]) || 0) & VIEW_CHANNEL) !== 0n;
|
|
198
|
+
const byId = new Map((overwrites || []).map((o) => [String(o.id), o]));
|
|
199
|
+
|
|
200
|
+
const everyone = byId.get(String(everyoneId));
|
|
201
|
+
if (everyone && has(everyone, 'allow')) return 'everyone';
|
|
202
|
+
|
|
203
|
+
for (const rank of RANK_LADDER) { // low -> high: first allow wins
|
|
204
|
+
const rid = roleMap[rank] ? String(roleMap[rank]) : null;
|
|
205
|
+
if (rid && has(byId.get(rid), 'allow')) return rank;
|
|
206
|
+
}
|
|
207
|
+
// @everyone explicitly DENIED but no rank allowed: restricted to nobody we
|
|
208
|
+
// manage. resolveViewRoles fails safe to archon, so mirror that rather than
|
|
209
|
+
// inventing wider access.
|
|
210
|
+
if (everyone && has(everyone, 'deny')) return 'archon';
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// configFromSnapshot — turn a LIVE guild snapshot into channels.json shape, so
|
|
215
|
+
// the file can be seeded from what the guild really is instead of from a guess
|
|
216
|
+
// (task 1003510; ADR 0037 §1 calls channels.json the single source of truth, and
|
|
217
|
+
// that is only true once the channels actually in the guild are written down).
|
|
218
|
+
//
|
|
219
|
+
// THE CORRECTNESS PROPERTY, and the reason this is worth having as a pure
|
|
220
|
+
// function: feeding the result straight back to planChannelReconcile against the
|
|
221
|
+
// SAME snapshot must produce ZERO ops. If any field is inverted wrongly — a view,
|
|
222
|
+
// a topic, a name, a parent — the planner emits an op for it, so the round trip
|
|
223
|
+
// is a total test of this function rather than a field-by-field restatement of it.
|
|
224
|
+
//
|
|
225
|
+
// That zero-ops guarantee holds for a guild the bot has already reconciled. On a
|
|
226
|
+
// guild it has NOT, a channel may carry no @everyone overwrite at all; the config
|
|
227
|
+
// then omits its `view`, it inherits the category, and the planner may rightly
|
|
228
|
+
// emit an edit. That difference is real and belongs in the operator's review, not
|
|
229
|
+
// hidden here — which is why `warnings` is returned rather than swallowed.
|
|
230
|
+
//
|
|
231
|
+
// Channels with no parent category are REPORTED, never invented: channels.json
|
|
232
|
+
// can only express a channel nested in a category, and the omitted-is-untouched
|
|
233
|
+
// rule (see the file's own header) makes leaving them out the safe default.
|
|
234
|
+
function configFromSnapshot(snapshot, ctx) {
|
|
235
|
+
const warnings = [];
|
|
236
|
+
const cats = (snapshot.categories || []).map((c) => ({
|
|
237
|
+
id: String(c.id),
|
|
238
|
+
name: c.name,
|
|
239
|
+
view: viewFromOverwrites(c.overwrites, ctx),
|
|
240
|
+
channels: [],
|
|
241
|
+
}));
|
|
242
|
+
const byId = new Map(cats.map((c) => [c.id, c]));
|
|
243
|
+
|
|
244
|
+
for (const ch of snapshot.channels || []) {
|
|
245
|
+
if (ch.type !== 0) continue; // text channels only, as snapshotFromGuild already filters
|
|
246
|
+
const parent = ch.parentId ? byId.get(String(ch.parentId)) : null;
|
|
247
|
+
if (!parent) {
|
|
248
|
+
warnings.push(`channel "${ch.name}" has no category — channels.json cannot express an uncategorised channel, so it is omitted (and therefore left untouched).`);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const view = viewFromOverwrites(ch.overwrites, ctx);
|
|
252
|
+
const entry = { key: slugify(ch.name), name: ch.name, topic: ch.topic || '' };
|
|
253
|
+
// Emit `view` only when it DIFFERS from the category's — the planner reads
|
|
254
|
+
// `ch.view || cat.view`, so an identical value is noise that drifts later.
|
|
255
|
+
if (view !== null && view !== parent.view) entry.view = view;
|
|
256
|
+
if (view === null) {
|
|
257
|
+
warnings.push(`channel "${ch.name}" carries no VIEW overwrite the bot manages — it inherits category "${parent.name}"; confirm that is intended before applying.`);
|
|
258
|
+
}
|
|
259
|
+
parent.channels.push(entry);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const categories = cats.map((c) => ({
|
|
263
|
+
name: c.name,
|
|
264
|
+
view: c.view === null ? 'everyone' : c.view,
|
|
265
|
+
channels: c.channels,
|
|
266
|
+
}));
|
|
267
|
+
for (const c of cats) {
|
|
268
|
+
if (c.view === null) warnings.push(`category "${c.name}" carries no VIEW overwrite the bot manages — recorded as "everyone"; confirm before applying.`);
|
|
269
|
+
}
|
|
270
|
+
return { config: { categories }, warnings };
|
|
271
|
+
}
|
|
272
|
+
|
|
181
273
|
// Ops never include deletions. Categories are planned before their channels so
|
|
182
274
|
// the apply step can resolve a freshly-created parent id.
|
|
183
275
|
function planChannelReconcile(snapshot, config, ctx) {
|
|
@@ -323,6 +415,18 @@ function computePlan(deps = realDeps) {
|
|
|
323
415
|
return { ok: true, ops, warnings, guildId: String(guild.id) };
|
|
324
416
|
}
|
|
325
417
|
|
|
418
|
+
// computeSnapshotConfig — what channels.json WOULD say if it described the guild
|
|
419
|
+
// as it is right now (task 1003510). Read-only: it touches Discord only through
|
|
420
|
+
// the same getGuild() the planner uses, and writes nothing anywhere.
|
|
421
|
+
function computeSnapshotConfig(deps = realDeps) {
|
|
422
|
+
const guild = deps.getGuild();
|
|
423
|
+
if (!guild) return { ok: false, reason: 'no_guild', config: null, warnings: [] };
|
|
424
|
+
const roleMap = deps.loadRoleMap();
|
|
425
|
+
const ctx = { everyoneId: String(guild.id), roleMap, botId: botIdFromGuild(guild) };
|
|
426
|
+
const { config, warnings } = configFromSnapshot(snapshotFromGuild(guild), ctx);
|
|
427
|
+
return { ok: true, config, warnings, guildId: String(guild.id) };
|
|
428
|
+
}
|
|
429
|
+
|
|
326
430
|
// Apply the plan to Discord. Best-effort, never throws. Returns a summary.
|
|
327
431
|
// Categories are created first so child channels can resolve their parent id.
|
|
328
432
|
async function reconcileChannels(deps = realDeps) {
|
|
@@ -398,6 +502,8 @@ module.exports = {
|
|
|
398
502
|
// pure (tested)
|
|
399
503
|
slugify,
|
|
400
504
|
resolveViewRoles,
|
|
505
|
+
viewFromOverwrites,
|
|
506
|
+
configFromSnapshot,
|
|
401
507
|
mergeViewOverwrites,
|
|
402
508
|
viewOverwritesEqual,
|
|
403
509
|
planChannelReconcile,
|
|
@@ -405,6 +511,7 @@ module.exports = {
|
|
|
405
511
|
loadConfig,
|
|
406
512
|
snapshotFromGuild,
|
|
407
513
|
computePlan,
|
|
514
|
+
computeSnapshotConfig,
|
|
408
515
|
reconcileChannels,
|
|
409
516
|
CONFIG_PATH,
|
|
410
517
|
VIEW_CHANNEL,
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
const express = require('express');
|
|
16
16
|
const api = require('../../../src/module-api');
|
|
17
|
+
// Structured + attributable, and the console ratchet counts raw console calls —
|
|
18
|
+
// the api.logger(<module key>) precedent every other module's routes follow.
|
|
19
|
+
const log = api.logger('discord');
|
|
17
20
|
const auth = require('../auth');
|
|
18
21
|
const db = require('../db');
|
|
19
22
|
const shipBroadcast = require('../ship-broadcast');
|
|
@@ -93,7 +96,28 @@ module.exports = function buildDiscordRouter() {
|
|
|
93
96
|
}
|
|
94
97
|
return res.json({ ok: true, guild_id: plan.guildId, op_count: plan.ops.length, ops: plan.ops, warnings: plan.warnings });
|
|
95
98
|
} catch (err) {
|
|
96
|
-
|
|
99
|
+
log.error({ err: err && err.message }, 'GET /discord/channels/plan failed');
|
|
100
|
+
return res.fail('internal_error', 500);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// GET /api/bongos/discord/channels/snapshot — what docs/discord/channels.json
|
|
105
|
+
// WOULD say if it described the guild exactly as it is right now (task 1003510).
|
|
106
|
+
// Read-only in both directions: it writes nothing to Discord and nothing to the
|
|
107
|
+
// repo — the operator reviews the output and commits it, so the layout stays
|
|
108
|
+
// Archon-gated by merge, which is the property ADR 0037 §1 rests on.
|
|
109
|
+
// 503 when the bot is disabled / not connected to a guild.
|
|
110
|
+
// rank: archon — channel administration command.
|
|
111
|
+
router.get('/discord/channels/snapshot', auth.requireBuilder, auth.requirePermission('discord.channels.manage'), async (req, res) => {
|
|
112
|
+
try {
|
|
113
|
+
const channels = require('../discord-channels');
|
|
114
|
+
const snap = channels.computeSnapshotConfig();
|
|
115
|
+
if (!snap.ok) {
|
|
116
|
+
return res.fail('bot_unavailable', 503, { reason: snap.reason });
|
|
117
|
+
}
|
|
118
|
+
return res.json({ ok: true, guild_id: snap.guildId, config: snap.config, warnings: snap.warnings });
|
|
119
|
+
} catch (err) {
|
|
120
|
+
log.error({ err: err && err.message }, 'GET /discord/channels/snapshot failed');
|
|
97
121
|
return res.fail('internal_error', 500);
|
|
98
122
|
}
|
|
99
123
|
});
|
|
@@ -111,7 +135,7 @@ module.exports = function buildDiscordRouter() {
|
|
|
111
135
|
}
|
|
112
136
|
return res.json({ ok: true, applied: result.applied, failed: result.failed, total: result.total, warnings: result.warnings });
|
|
113
137
|
} catch (err) {
|
|
114
|
-
|
|
138
|
+
log.error({ err: err && err.message }, 'POST /discord/channels/reconcile failed');
|
|
115
139
|
return res.fail('internal_error', 500);
|
|
116
140
|
}
|
|
117
141
|
});
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.676",
|
|
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.676",
|
|
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.676",
|
|
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",
|