@bongos/core 1.19.673 → 1.19.675
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 +35 -25
- 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 +6 -3
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +71 -12
- package/docs/api-reference.md +4 -3
- package/docs/module-api-changelog.md +4 -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/modules/lifecycle/db-tasks.js +22 -0
- package/modules/lifecycle/goal-edits.js +34 -0
- package/modules/lifecycle/routes/done-when.js +114 -17
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/discord-channels.js +14 -3
- package/src/module-api.js +1 -1
- package/tests/criterion_goal_attach.mjs +347 -0
- package/tests/discord_channels_snapshot.mjs +198 -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.675",
|
|
6
|
+
"core_contract": "1.19.675",
|
|
7
|
+
"source_commit": "1341cb9d37a0965db7f80483f7f1cb8f3622b2c5",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-11T19:32:52.079Z",
|
|
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": "554ecf20aaad28b8461021791d1e5c4d2c890a860a458885227f20429b946339",
|
|
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": "af50f99c27d85db0067d45e5e6eba133edcae8b3dd7388041b6824f4cb192e22"
|
|
2796
2796
|
},
|
|
2797
2797
|
{
|
|
2798
2798
|
"path": "docs/modules-contract.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",
|
|
@@ -5692,7 +5692,7 @@
|
|
|
5692
5692
|
{
|
|
5693
5693
|
"path": "modules/lifecycle/db-tasks.js",
|
|
5694
5694
|
"mode": "0000644",
|
|
5695
|
-
"sha256": "
|
|
5695
|
+
"sha256": "5b2df46e54dbeb0694a805d9743df5da6fde90b73a76989e917257b7f78f8ed9"
|
|
5696
5696
|
},
|
|
5697
5697
|
{
|
|
5698
5698
|
"path": "modules/lifecycle/db-versions.js",
|
|
@@ -5747,7 +5747,7 @@
|
|
|
5747
5747
|
{
|
|
5748
5748
|
"path": "modules/lifecycle/goal-edits.js",
|
|
5749
5749
|
"mode": "0000644",
|
|
5750
|
-
"sha256": "
|
|
5750
|
+
"sha256": "f00d1bb6bd57624679295f33d55ad1d81a9479789a78d4325f13948b9559f9f5"
|
|
5751
5751
|
},
|
|
5752
5752
|
{
|
|
5753
5753
|
"path": "modules/lifecycle/goal-rollup.js",
|
|
@@ -5872,7 +5872,7 @@
|
|
|
5872
5872
|
{
|
|
5873
5873
|
"path": "modules/lifecycle/routes/done-when.js",
|
|
5874
5874
|
"mode": "0000644",
|
|
5875
|
-
"sha256": "
|
|
5875
|
+
"sha256": "9370fd1fc8a4a1ff864c1e85321b05a74bb45150ef8eb92cefdbbd85524bbe27"
|
|
5876
5876
|
},
|
|
5877
5877
|
{
|
|
5878
5878
|
"path": "modules/lifecycle/routes/gate-approvals.js",
|
|
@@ -7767,12 +7767,12 @@
|
|
|
7767
7767
|
{
|
|
7768
7768
|
"path": "package-lock.json",
|
|
7769
7769
|
"mode": "0000644",
|
|
7770
|
-
"sha256": "
|
|
7770
|
+
"sha256": "795b9955321b5ecffd9d78f515649442016285cb21f728c4001ee97ebcc32ac5"
|
|
7771
7771
|
},
|
|
7772
7772
|
{
|
|
7773
7773
|
"path": "package.json",
|
|
7774
7774
|
"mode": "0000644",
|
|
7775
|
-
"sha256": "
|
|
7775
|
+
"sha256": "33bcb1180ab5e54281e7c50d39a712d142b259f77f37b685f2bb4512b1c010fa"
|
|
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",
|
|
@@ -9532,7 +9532,7 @@
|
|
|
9532
9532
|
{
|
|
9533
9533
|
"path": "src/module-api.js",
|
|
9534
9534
|
"mode": "0000644",
|
|
9535
|
-
"sha256": "
|
|
9535
|
+
"sha256": "35f42575db36eb956e2479c681562e18a93d2d4973c8df0d79ac5a54048d4542"
|
|
9536
9536
|
},
|
|
9537
9537
|
{
|
|
9538
9538
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10284,6 +10284,11 @@
|
|
|
10284
10284
|
"mode": "0000644",
|
|
10285
10285
|
"sha256": "970db80cf43dba855bf9bd331434add458ab16e719f06710487094a3a578ec53"
|
|
10286
10286
|
},
|
|
10287
|
+
{
|
|
10288
|
+
"path": "tests/criterion_goal_attach.mjs",
|
|
10289
|
+
"mode": "0000644",
|
|
10290
|
+
"sha256": "979589fd08dc41da3c837513452353aa87f7653620fad22c8a9115d4d7d13b90"
|
|
10291
|
+
},
|
|
10287
10292
|
{
|
|
10288
10293
|
"path": "tests/criterion_progress.mjs",
|
|
10289
10294
|
"mode": "0000644",
|
|
@@ -10434,6 +10439,11 @@
|
|
|
10434
10439
|
"mode": "0000644",
|
|
10435
10440
|
"sha256": "981d34c2fb5ecb6e9b9f38ece947c6c7a400dceadf8bbcf09546c8b3a92eb899"
|
|
10436
10441
|
},
|
|
10442
|
+
{
|
|
10443
|
+
"path": "tests/discord_channels_snapshot.mjs",
|
|
10444
|
+
"mode": "0000644",
|
|
10445
|
+
"sha256": "d4e43501ac29ea920c0c064afe601bd9fc24f87808f10986a51d981b3111946b"
|
|
10446
|
+
},
|
|
10437
10447
|
{
|
|
10438
10448
|
"path": "tests/discord_foundation.mjs",
|
|
10439
10449
|
"mode": "0000644",
|
|
@@ -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 }
|
|
@@ -150,8 +151,8 @@ export interface PatchBuildersIdBudgetResponse { builder: unknown }
|
|
|
150
151
|
export interface PatchBuildersIdRankRequest { rank: string }
|
|
151
152
|
export interface PatchBuildersIdRankResponse { builder: unknown; previous_rank: unknown; direction: unknown; credit_delta: unknown; broadcast: unknown }
|
|
152
153
|
export interface PatchBuildersIdStatusRequest { status: "active" | "inactive"; reason?: string }
|
|
153
|
-
export interface PatchDoneWhenCriterionIdRequest { criterion_md
|
|
154
|
-
export interface PatchDoneWhenCriterionIdResponse { ok: boolean; criterion: unknown; deltas: unknown; unchanged:
|
|
154
|
+
export interface PatchDoneWhenCriterionIdRequest { criterion_md?: string; goal_id?: number }
|
|
155
|
+
export interface PatchDoneWhenCriterionIdResponse { ok: boolean; criterion: unknown; deltas: unknown; unchanged: unknown; achieved_goals: unknown }
|
|
155
156
|
export interface PatchGoalsIdMembersBuilderIdRequest { membership_kind: string }
|
|
156
157
|
export interface PatchGoalsIdMembersBuilderIdResponse { ok: boolean; member: unknown; changed: boolean }
|
|
157
158
|
export interface PatchHelpRequestsIdRequest { status: "answered" | "withdrawn" }
|
|
@@ -647,10 +648,12 @@ 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 */
|
|
653
|
-
patchDoneWhenCriterionId(args
|
|
656
|
+
patchDoneWhenCriterionId(args?: RequestArgs & { body?: PatchDoneWhenCriterionIdRequest }): Promise<PatchDoneWhenCriterionIdResponse>;
|
|
654
657
|
/** POST /done-when/{criterionId}/satisfy — rank: metic+archon */
|
|
655
658
|
postDoneWhenCriterionIdSatisfy(args?: RequestArgs & { body?: PostDoneWhenCriterionIdSatisfyRequest }): Promise<ApiResponse>;
|
|
656
659
|
/** POST /done-when/{criterionId}/unsatisfy — 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",
|
|
@@ -4386,7 +4427,7 @@
|
|
|
4386
4427
|
"done-when"
|
|
4387
4428
|
],
|
|
4388
4429
|
"summary": "PATCH /done-when/:criterionId",
|
|
4389
|
-
"description": "PATCH /done-when/:criterionId —
|
|
4430
|
+
"description": "PATCH /done-when/:criterionId — amend a criterion's TEXT (task 1003102) and/or MOVE it between goals (task 1003509). Was write-once: this file exposed create, satisfy and unsatisfy and nothing else, so wrong text had no API repair path. The incident that forced it: a shell-quoting error stripped backtick-wrapped literals out of three criteria on goal 1000073 at CREATION time — the intent was recorded, the text was wrong, and the only channels were prod SQL or archiving the goal (worse: criteria are version-level rows, so an archived goal's criteria linger in the version rollup). A SATISFIED criterion is REFUSED, by name — see prose-edits.criterionProseRefusal for why that is a decision and not a limitation: a satisfied criterion is a claim someone verified, and re-wording it re-points that verification at text nobody checked. Unsatisfy → edit → re-satisfy makes the undo a recorded act. Gated like criterion CREATION — BOTH HALVES. The permission `criterion.create` is only the RANK floor; the sibling POST /versions/:id/done-when also runs authorizeCriterionCreate, the per-goal owner|manager wall (ADR 0154). Copying just the rank half would let any Metic rewrite criteria on a goal they neither own, manage, nor belong to — reusing the SAME pure gate is the only way the claim \"gated like creation\" is true. (Caught by the grader on the first cut of task 1003102: the comment said it, the code did half of it.) rank: metic+archon — matches POST /versions/:id/done-when. THE GOAL MOVE (task 1003509 — walk finding W5). `goal_id` used to be writable only by createCriterion's INSERT, so a criterion was born linked and stayed that way for life. The runner seeds a fresh instance's V1 criterion with NO goal — at standup there is no goal yet to name (provision-repo.js seedFirstVersionCmd) — so the owner's first goal could never adopt it: /progress showed the criterion \"(ungrouped)\" forever beside the goal it obviously belonged to, reporting `criteria: []`. Amending it took SQL, and a hub-provisioned owner has no SQL access at all. BOTH ENDS OF A MOVE ARE GATED, not just the goal it leaves. A move is a criterion write on the DESTINATION every bit as much as on the source, and gating only the source would let a goal's own owner push their criterion into a goal they have no authority over — a criterion being, per the CREATE route above, a thing that permanently blocks version close and goal auto-achieve with no delete route to undo it. So authorizeCriterionCreate runs against both and both must pass. DETACH (`goal_id: null`) has no destination, so it lands on that gate's goal-less branch and is Archon-only — the same wall that makes version-level criteria Archon-only at creation (ADR 0154), reached without a rule of its own. A SATISFIED criterion may still MOVE. criterionProseRefusal freezes the text of a verified claim, and that reasoning is about re-wording: moving one between goals changes how it is grouped, never what was checked. It is refused here only when the prose is what is changing. WHO CAN ADOPT THE SEEDED CRITERION. Because a goal-LESS criterion is version-level, the source gate's own goal-less branch makes ANY write to one Archon-only — which predates this task (a non-Archon could not edit an ungrouped criterion's prose either). That is not a wall in front of W5: the founder of a wizard-born instance IS its Archon, so the one criterion this was filed for is theirs to place. A Metic is unaffected where it matters — regrouping between two goals they already hold needs no escalation.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `criterion.create` (all required).",
|
|
4390
4431
|
"x-rank": "metic+archon",
|
|
4391
4432
|
"x-source": "modules/lifecycle/routes/done-when.js",
|
|
4392
4433
|
"x-permissions": [
|
|
@@ -4404,7 +4445,7 @@
|
|
|
4404
4445
|
}
|
|
4405
4446
|
],
|
|
4406
4447
|
"requestBody": {
|
|
4407
|
-
"required":
|
|
4448
|
+
"required": false,
|
|
4408
4449
|
"content": {
|
|
4409
4450
|
"application/json": {
|
|
4410
4451
|
"schema": {
|
|
@@ -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": {
|
|
@@ -18447,11 +18505,12 @@
|
|
|
18447
18505
|
"type": "string",
|
|
18448
18506
|
"minLength": 1,
|
|
18449
18507
|
"maxLength": 10000
|
|
18508
|
+
},
|
|
18509
|
+
"goal_id": {
|
|
18510
|
+
"type": "integer",
|
|
18511
|
+
"minimum": 1
|
|
18450
18512
|
}
|
|
18451
18513
|
},
|
|
18452
|
-
"required": [
|
|
18453
|
-
"criterion_md"
|
|
18454
|
-
],
|
|
18455
18514
|
"additionalProperties": false
|
|
18456
18515
|
},
|
|
18457
18516
|
"PatchDoneWhenCriterionIdResponse": {
|
|
@@ -18462,15 +18521,15 @@
|
|
|
18462
18521
|
},
|
|
18463
18522
|
"criterion": {},
|
|
18464
18523
|
"deltas": {},
|
|
18465
|
-
"unchanged": {
|
|
18466
|
-
|
|
18467
|
-
}
|
|
18524
|
+
"unchanged": {},
|
|
18525
|
+
"achieved_goals": {}
|
|
18468
18526
|
},
|
|
18469
18527
|
"required": [
|
|
18470
18528
|
"ok",
|
|
18471
18529
|
"criterion",
|
|
18472
18530
|
"deltas",
|
|
18473
|
-
"unchanged"
|
|
18531
|
+
"unchanged",
|
|
18532
|
+
"achieved_goals"
|
|
18474
18533
|
]
|
|
18475
18534
|
},
|
|
18476
18535
|
"PatchGoalsIdMembersBuilderIdRequest": {
|
|
@@ -22954,9 +23013,9 @@
|
|
|
22954
23013
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
22955
23014
|
}
|
|
22956
23015
|
},
|
|
22957
|
-
"x-endpoint-count":
|
|
22958
|
-
"x-schema-count":
|
|
23016
|
+
"x-endpoint-count": 363,
|
|
23017
|
+
"x-schema-count": 402,
|
|
22959
23018
|
"x-undocumented-bodies": 9,
|
|
22960
|
-
"x-response-schemas":
|
|
23019
|
+
"x-response-schemas": 271,
|
|
22961
23020
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
22962
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,18 +191,19 @@ 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
|
|
|
203
204
|
| Method | Path | Rank | Body | Description |
|
|
204
205
|
|---|---|---|---|---|
|
|
205
|
-
| PATCH | `/api/bongos/done-when/:criterionId` | `metic+archon` | `criterion_md` | PATCH /done-when/:criterionId —
|
|
206
|
+
| PATCH | `/api/bongos/done-when/:criterionId` | `metic+archon` | `criterion_md`, `goal_id` | PATCH /done-when/:criterionId — amend a criterion's TEXT (task 1003102) and/or MOVE it between goals (task 1003509). |
|
|
206
207
|
| POST | `/api/bongos/done-when/:criterionId/satisfy` | `metic+archon` | `satisfied_by_task_id` | Auth (Metic+): confirm a criterion — flip it to satisfied. |
|
|
207
208
|
| POST | `/api/bongos/done-when/:criterionId/unsatisfy` | `metic+archon` | — | Auth: flip a criterion back to unsatisfied (rare; for corrections). |
|
|
208
209
|
| GET | `/api/bongos/done-when/pending-review` | `metic+archon` | — | Auth (Metic+): the criterion review queue — every criterion auto-flagged "met — pending review" (≥1 linked task, all shipped, not yet con… |
|
|
@@ -1805,5 +1805,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1805
1805
|
landed since 1.19.671 with no explicit bump. run 34629971417. (task 1002620)
|
|
1806
1806
|
1.19.673 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1807
1807
|
landed since 1.19.672 with no explicit bump. run 34632328471. (task 1002620)
|
|
1808
|
+
1.19.674 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
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)
|
|
1808
1812
|
---------------------------------------------------------------------------
|
|
1809
1813
|
```
|
|
@@ -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,
|