@bongos/core 1.19.358 → 1.19.359
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 +42 -22
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +12 -0
- package/clients/bongos-client/index.cjs +12 -0
- package/clients/bongos-client/index.d.ts +19 -0
- package/clients/bongos-client/index.mjs +12 -0
- package/docs/api/openapi.json +345 -3
- package/docs/api-reference.md +12 -2
- package/modules/builder-settings/builder-needs.js +29 -2
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +12 -0
- package/modules/lifecycle/lifecycle.js +5 -0
- package/modules/lifecycle/migrations/lifecycle_005_task_recommendations.sql +90 -0
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/routes/recommendations.js +148 -0
- package/modules/lifecycle/task-recommendations.js +247 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/routes/me.js +15 -0
- package/src/module-api.js +3 -1
- package/tests/task_recommendations.mjs +297 -0
package/docs/api/openapi.json
CHANGED
|
@@ -205,6 +205,10 @@
|
|
|
205
205
|
"name": "sso",
|
|
206
206
|
"description": "Endpoints under `/api/bongos/sso`."
|
|
207
207
|
},
|
|
208
|
+
{
|
|
209
|
+
"name": "task-recommendations",
|
|
210
|
+
"description": "Endpoints under `/api/bongos/task-recommendations`."
|
|
211
|
+
},
|
|
208
212
|
{
|
|
209
213
|
"name": "task-visuals",
|
|
210
214
|
"description": "Endpoints under `/api/bongos/task-visuals`."
|
|
@@ -12092,6 +12096,194 @@
|
|
|
12092
12096
|
"security": []
|
|
12093
12097
|
}
|
|
12094
12098
|
},
|
|
12099
|
+
"/task-recommendations": {
|
|
12100
|
+
"post": {
|
|
12101
|
+
"operationId": "post_task_recommendations",
|
|
12102
|
+
"tags": [
|
|
12103
|
+
"task-recommendations"
|
|
12104
|
+
],
|
|
12105
|
+
"summary": "POST /task-recommendations",
|
|
12106
|
+
"description": "recommending is not a privileged act. It grants the recipient nothing they did not already have (the task was claimable by them or it was not), so gating it by rank would only stop newcomers from pointing at something useful.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
12107
|
+
"x-rank": "any-builder",
|
|
12108
|
+
"x-source": "modules/lifecycle/routes/recommendations.js",
|
|
12109
|
+
"requestBody": {
|
|
12110
|
+
"required": true,
|
|
12111
|
+
"content": {
|
|
12112
|
+
"application/json": {
|
|
12113
|
+
"schema": {
|
|
12114
|
+
"$ref": "#/components/schemas/PostTaskRecommendationsRequest"
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
},
|
|
12118
|
+
"x-validated": true
|
|
12119
|
+
},
|
|
12120
|
+
"responses": {
|
|
12121
|
+
"200": {
|
|
12122
|
+
"description": "Success.",
|
|
12123
|
+
"content": {
|
|
12124
|
+
"application/json": {
|
|
12125
|
+
"schema": {
|
|
12126
|
+
"$ref": "#/components/schemas/PostTaskRecommendationsResponse"
|
|
12127
|
+
}
|
|
12128
|
+
}
|
|
12129
|
+
}
|
|
12130
|
+
},
|
|
12131
|
+
"400": {
|
|
12132
|
+
"$ref": "#/components/responses/ValidationFailed"
|
|
12133
|
+
},
|
|
12134
|
+
"401": {
|
|
12135
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
12136
|
+
},
|
|
12137
|
+
"403": {
|
|
12138
|
+
"$ref": "#/components/responses/Forbidden"
|
|
12139
|
+
}
|
|
12140
|
+
},
|
|
12141
|
+
"security": [
|
|
12142
|
+
{
|
|
12143
|
+
"builderSession": []
|
|
12144
|
+
}
|
|
12145
|
+
]
|
|
12146
|
+
}
|
|
12147
|
+
},
|
|
12148
|
+
"/task-recommendations/for-me": {
|
|
12149
|
+
"get": {
|
|
12150
|
+
"operationId": "get_task_recommendations_for_me",
|
|
12151
|
+
"tags": [
|
|
12152
|
+
"task-recommendations"
|
|
12153
|
+
],
|
|
12154
|
+
"summary": "GET /task-recommendations/for-me",
|
|
12155
|
+
"description": "you only ever see what is addressed to you. NOTE WHAT THIS RESPONSE DOES NOT CONTAIN: any signal that the recipient now holds the task. It carries the task's own status so the recipient can judge, and that is all. Claiming is a separate, deliberate act on their part.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
12156
|
+
"x-rank": "any-builder",
|
|
12157
|
+
"x-source": "modules/lifecycle/routes/recommendations.js",
|
|
12158
|
+
"responses": {
|
|
12159
|
+
"200": {
|
|
12160
|
+
"description": "Success.",
|
|
12161
|
+
"content": {
|
|
12162
|
+
"application/json": {
|
|
12163
|
+
"schema": {
|
|
12164
|
+
"$ref": "#/components/schemas/GetTaskRecommendationsForMeResponse"
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
12167
|
+
}
|
|
12168
|
+
},
|
|
12169
|
+
"400": {
|
|
12170
|
+
"$ref": "#/components/responses/BadRequest"
|
|
12171
|
+
},
|
|
12172
|
+
"401": {
|
|
12173
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
12174
|
+
},
|
|
12175
|
+
"403": {
|
|
12176
|
+
"$ref": "#/components/responses/Forbidden"
|
|
12177
|
+
}
|
|
12178
|
+
},
|
|
12179
|
+
"security": [
|
|
12180
|
+
{
|
|
12181
|
+
"builderSession": []
|
|
12182
|
+
}
|
|
12183
|
+
]
|
|
12184
|
+
}
|
|
12185
|
+
},
|
|
12186
|
+
"/task-recommendations/mine": {
|
|
12187
|
+
"get": {
|
|
12188
|
+
"operationId": "get_task_recommendations_mine",
|
|
12189
|
+
"tags": [
|
|
12190
|
+
"task-recommendations"
|
|
12191
|
+
],
|
|
12192
|
+
"summary": "GET /task-recommendations/mine",
|
|
12193
|
+
"description": "what I recommended, and what became of it. Unfiltered by default: a declined recommendation stays readable, which is the criterion's \"the recommendation and its author are readable afterwards\".\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
12194
|
+
"x-rank": "any-builder",
|
|
12195
|
+
"x-source": "modules/lifecycle/routes/recommendations.js",
|
|
12196
|
+
"responses": {
|
|
12197
|
+
"200": {
|
|
12198
|
+
"description": "Success.",
|
|
12199
|
+
"content": {
|
|
12200
|
+
"application/json": {
|
|
12201
|
+
"schema": {
|
|
12202
|
+
"$ref": "#/components/schemas/GetTaskRecommendationsMineResponse"
|
|
12203
|
+
}
|
|
12204
|
+
}
|
|
12205
|
+
}
|
|
12206
|
+
},
|
|
12207
|
+
"400": {
|
|
12208
|
+
"$ref": "#/components/responses/BadRequest"
|
|
12209
|
+
},
|
|
12210
|
+
"401": {
|
|
12211
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
12212
|
+
},
|
|
12213
|
+
"403": {
|
|
12214
|
+
"$ref": "#/components/responses/Forbidden"
|
|
12215
|
+
}
|
|
12216
|
+
},
|
|
12217
|
+
"security": [
|
|
12218
|
+
{
|
|
12219
|
+
"builderSession": []
|
|
12220
|
+
}
|
|
12221
|
+
]
|
|
12222
|
+
}
|
|
12223
|
+
},
|
|
12224
|
+
"/task-recommendations/{id}": {
|
|
12225
|
+
"patch": {
|
|
12226
|
+
"operationId": "patch_task_recommendations_id",
|
|
12227
|
+
"tags": [
|
|
12228
|
+
"task-recommendations"
|
|
12229
|
+
],
|
|
12230
|
+
"summary": "PATCH /task-recommendations/:id",
|
|
12231
|
+
"description": "Decline. Costs nothing, and this route is where that is visible: it flips a status and returns the row. No karma call, no counter, no notification to the sender beyond the row itself turning up in their own list. rank: any-builder — ownership is enforced in the UPDATE's WHERE, not by rank.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
12232
|
+
"x-rank": "any-builder",
|
|
12233
|
+
"x-source": "modules/lifecycle/routes/recommendations.js",
|
|
12234
|
+
"parameters": [
|
|
12235
|
+
{
|
|
12236
|
+
"name": "id",
|
|
12237
|
+
"in": "path",
|
|
12238
|
+
"required": true,
|
|
12239
|
+
"schema": {
|
|
12240
|
+
"type": "string"
|
|
12241
|
+
},
|
|
12242
|
+
"description": "Path parameter `id`."
|
|
12243
|
+
}
|
|
12244
|
+
],
|
|
12245
|
+
"requestBody": {
|
|
12246
|
+
"required": true,
|
|
12247
|
+
"content": {
|
|
12248
|
+
"application/json": {
|
|
12249
|
+
"schema": {
|
|
12250
|
+
"$ref": "#/components/schemas/PatchTaskRecommendationsIdRequest"
|
|
12251
|
+
}
|
|
12252
|
+
}
|
|
12253
|
+
},
|
|
12254
|
+
"x-validated": true
|
|
12255
|
+
},
|
|
12256
|
+
"responses": {
|
|
12257
|
+
"200": {
|
|
12258
|
+
"description": "Success.",
|
|
12259
|
+
"content": {
|
|
12260
|
+
"application/json": {
|
|
12261
|
+
"schema": {
|
|
12262
|
+
"$ref": "#/components/schemas/PatchTaskRecommendationsIdResponse"
|
|
12263
|
+
}
|
|
12264
|
+
}
|
|
12265
|
+
}
|
|
12266
|
+
},
|
|
12267
|
+
"400": {
|
|
12268
|
+
"$ref": "#/components/responses/ValidationFailed"
|
|
12269
|
+
},
|
|
12270
|
+
"401": {
|
|
12271
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
12272
|
+
},
|
|
12273
|
+
"403": {
|
|
12274
|
+
"$ref": "#/components/responses/Forbidden"
|
|
12275
|
+
},
|
|
12276
|
+
"404": {
|
|
12277
|
+
"$ref": "#/components/responses/NotFound"
|
|
12278
|
+
}
|
|
12279
|
+
},
|
|
12280
|
+
"security": [
|
|
12281
|
+
{
|
|
12282
|
+
"builderSession": []
|
|
12283
|
+
}
|
|
12284
|
+
]
|
|
12285
|
+
}
|
|
12286
|
+
},
|
|
12095
12287
|
"/task-visuals/{name}": {
|
|
12096
12288
|
"get": {
|
|
12097
12289
|
"operationId": "get_task_visuals_name",
|
|
@@ -13431,6 +13623,58 @@
|
|
|
13431
13623
|
]
|
|
13432
13624
|
}
|
|
13433
13625
|
},
|
|
13626
|
+
"/tasks/{id}/recommendations": {
|
|
13627
|
+
"get": {
|
|
13628
|
+
"operationId": "get_tasks_id_recommendations",
|
|
13629
|
+
"tags": [
|
|
13630
|
+
"tasks"
|
|
13631
|
+
],
|
|
13632
|
+
"summary": "GET /tasks/:id/recommendations",
|
|
13633
|
+
"description": "The per-task audit read: who recommended this task, to whom, and why. rank: any-builder, and deliberately NOT scoped to the two parties. The criterion's argument for a ledger is that an untraced recommendation is indistinguishable from a task quietly changing hands — a trace only the two people involved can read does not answer that. Tasks are already visible to every builder, and a recommendation says nothing more private than \"this person thought that person should look at this\".\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
13634
|
+
"x-rank": "any-builder",
|
|
13635
|
+
"x-source": "modules/lifecycle/routes/recommendations.js",
|
|
13636
|
+
"parameters": [
|
|
13637
|
+
{
|
|
13638
|
+
"name": "id",
|
|
13639
|
+
"in": "path",
|
|
13640
|
+
"required": true,
|
|
13641
|
+
"schema": {
|
|
13642
|
+
"type": "string"
|
|
13643
|
+
},
|
|
13644
|
+
"description": "Path parameter `id`."
|
|
13645
|
+
}
|
|
13646
|
+
],
|
|
13647
|
+
"responses": {
|
|
13648
|
+
"200": {
|
|
13649
|
+
"description": "Success.",
|
|
13650
|
+
"content": {
|
|
13651
|
+
"application/json": {
|
|
13652
|
+
"schema": {
|
|
13653
|
+
"$ref": "#/components/schemas/GetTasksIdRecommendationsResponse"
|
|
13654
|
+
}
|
|
13655
|
+
}
|
|
13656
|
+
}
|
|
13657
|
+
},
|
|
13658
|
+
"400": {
|
|
13659
|
+
"$ref": "#/components/responses/BadRequest"
|
|
13660
|
+
},
|
|
13661
|
+
"401": {
|
|
13662
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
13663
|
+
},
|
|
13664
|
+
"403": {
|
|
13665
|
+
"$ref": "#/components/responses/Forbidden"
|
|
13666
|
+
},
|
|
13667
|
+
"404": {
|
|
13668
|
+
"$ref": "#/components/responses/NotFound"
|
|
13669
|
+
}
|
|
13670
|
+
},
|
|
13671
|
+
"security": [
|
|
13672
|
+
{
|
|
13673
|
+
"builderSession": []
|
|
13674
|
+
}
|
|
13675
|
+
]
|
|
13676
|
+
}
|
|
13677
|
+
},
|
|
13434
13678
|
"/tasks/{id}/ship": {
|
|
13435
13679
|
"post": {
|
|
13436
13680
|
"operationId": "post_tasks_id_ship",
|
|
@@ -15307,6 +15551,28 @@
|
|
|
15307
15551
|
"public_key_b64"
|
|
15308
15552
|
]
|
|
15309
15553
|
},
|
|
15554
|
+
"GetTaskRecommendationsForMeResponse": {
|
|
15555
|
+
"type": "object",
|
|
15556
|
+
"properties": {
|
|
15557
|
+
"recommendations": {},
|
|
15558
|
+
"count": {}
|
|
15559
|
+
},
|
|
15560
|
+
"required": [
|
|
15561
|
+
"recommendations",
|
|
15562
|
+
"count"
|
|
15563
|
+
]
|
|
15564
|
+
},
|
|
15565
|
+
"GetTaskRecommendationsMineResponse": {
|
|
15566
|
+
"type": "object",
|
|
15567
|
+
"properties": {
|
|
15568
|
+
"recommendations": {},
|
|
15569
|
+
"count": {}
|
|
15570
|
+
},
|
|
15571
|
+
"required": [
|
|
15572
|
+
"recommendations",
|
|
15573
|
+
"count"
|
|
15574
|
+
]
|
|
15575
|
+
},
|
|
15310
15576
|
"GetTasksClaimableResponse": {
|
|
15311
15577
|
"type": "object",
|
|
15312
15578
|
"properties": {
|
|
@@ -15338,6 +15604,17 @@
|
|
|
15338
15604
|
"dependents"
|
|
15339
15605
|
]
|
|
15340
15606
|
},
|
|
15607
|
+
"GetTasksIdRecommendationsResponse": {
|
|
15608
|
+
"type": "object",
|
|
15609
|
+
"properties": {
|
|
15610
|
+
"recommendations": {},
|
|
15611
|
+
"count": {}
|
|
15612
|
+
},
|
|
15613
|
+
"required": [
|
|
15614
|
+
"recommendations",
|
|
15615
|
+
"count"
|
|
15616
|
+
]
|
|
15617
|
+
},
|
|
15341
15618
|
"GetTasksNewcomerFloorResponse": {
|
|
15342
15619
|
"type": "object",
|
|
15343
15620
|
"properties": {
|
|
@@ -15925,6 +16202,35 @@
|
|
|
15925
16202
|
"push"
|
|
15926
16203
|
]
|
|
15927
16204
|
},
|
|
16205
|
+
"PatchTaskRecommendationsIdRequest": {
|
|
16206
|
+
"type": "object",
|
|
16207
|
+
"properties": {
|
|
16208
|
+
"status": {
|
|
16209
|
+
"type": "string",
|
|
16210
|
+
"enum": [
|
|
16211
|
+
"declined",
|
|
16212
|
+
"withdrawn"
|
|
16213
|
+
]
|
|
16214
|
+
}
|
|
16215
|
+
},
|
|
16216
|
+
"required": [
|
|
16217
|
+
"status"
|
|
16218
|
+
],
|
|
16219
|
+
"additionalProperties": false
|
|
16220
|
+
},
|
|
16221
|
+
"PatchTaskRecommendationsIdResponse": {
|
|
16222
|
+
"type": "object",
|
|
16223
|
+
"properties": {
|
|
16224
|
+
"ok": {
|
|
16225
|
+
"type": "boolean"
|
|
16226
|
+
},
|
|
16227
|
+
"recommendation": {}
|
|
16228
|
+
},
|
|
16229
|
+
"required": [
|
|
16230
|
+
"ok",
|
|
16231
|
+
"recommendation"
|
|
16232
|
+
]
|
|
16233
|
+
},
|
|
15928
16234
|
"PatchTasksIdRequest": {
|
|
15929
16235
|
"type": "object",
|
|
15930
16236
|
"properties": {
|
|
@@ -18777,6 +19083,42 @@
|
|
|
18777
19083
|
"avatar_url"
|
|
18778
19084
|
]
|
|
18779
19085
|
},
|
|
19086
|
+
"PostTaskRecommendationsRequest": {
|
|
19087
|
+
"type": "object",
|
|
19088
|
+
"properties": {
|
|
19089
|
+
"task_id": {
|
|
19090
|
+
"type": "integer",
|
|
19091
|
+
"minimum": 1
|
|
19092
|
+
},
|
|
19093
|
+
"recommended_to": {
|
|
19094
|
+
"type": "integer",
|
|
19095
|
+
"minimum": 1
|
|
19096
|
+
},
|
|
19097
|
+
"reason": {
|
|
19098
|
+
"type": "string",
|
|
19099
|
+
"minLength": 1
|
|
19100
|
+
}
|
|
19101
|
+
},
|
|
19102
|
+
"required": [
|
|
19103
|
+
"task_id",
|
|
19104
|
+
"recommended_to",
|
|
19105
|
+
"reason"
|
|
19106
|
+
],
|
|
19107
|
+
"additionalProperties": false
|
|
19108
|
+
},
|
|
19109
|
+
"PostTaskRecommendationsResponse": {
|
|
19110
|
+
"type": "object",
|
|
19111
|
+
"properties": {
|
|
19112
|
+
"ok": {
|
|
19113
|
+
"type": "boolean"
|
|
19114
|
+
},
|
|
19115
|
+
"recommendation": {}
|
|
19116
|
+
},
|
|
19117
|
+
"required": [
|
|
19118
|
+
"ok",
|
|
19119
|
+
"recommendation"
|
|
19120
|
+
]
|
|
19121
|
+
},
|
|
18780
19122
|
"PostTasksIdAbandonRequest": {
|
|
18781
19123
|
"type": "object",
|
|
18782
19124
|
"properties": {
|
|
@@ -19452,9 +19794,9 @@
|
|
|
19452
19794
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
19453
19795
|
}
|
|
19454
19796
|
},
|
|
19455
|
-
"x-endpoint-count":
|
|
19456
|
-
"x-schema-count":
|
|
19797
|
+
"x-endpoint-count": 312,
|
|
19798
|
+
"x-schema-count": 350,
|
|
19457
19799
|
"x-undocumented-bodies": 8,
|
|
19458
|
-
"x-response-schemas":
|
|
19800
|
+
"x-response-schemas": 234,
|
|
19459
19801
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
19460
19802
|
}
|
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. 312 endpoints across 51 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`.
|
|
@@ -507,13 +507,22 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
507
507
|
| GET | `/api/bongos/sso/pubkey` | `public` | — | GET /sso/pubkey — the hub's Ed25519 assertion-verification key (public). |
|
|
508
508
|
| POST | `/api/bongos/sso/token` | `public` | `client_id`, `client_secret`, `code`, `redirect_uri` | POST /sso/token — server-to-server code redemption. |
|
|
509
509
|
|
|
510
|
+
## `task-recommendations` (4)
|
|
511
|
+
|
|
512
|
+
| Method | Path | Rank | Body | Description |
|
|
513
|
+
|---|---|---|---|---|
|
|
514
|
+
| POST | `/api/bongos/task-recommendations` | `any-builder` | `task_id`, `recommended_to`, `reason` | recommending is not a privileged act. |
|
|
515
|
+
| PATCH | `/api/bongos/task-recommendations/:id` | `any-builder` | `status` | Decline. |
|
|
516
|
+
| GET | `/api/bongos/task-recommendations/for-me` | `any-builder` | — | you only ever see what is addressed to you. |
|
|
517
|
+
| GET | `/api/bongos/task-recommendations/mine` | `any-builder` | — | what I recommended, and what became of it. |
|
|
518
|
+
|
|
510
519
|
## `task-visuals` (1)
|
|
511
520
|
|
|
512
521
|
| Method | Path | Rank | Body | Description |
|
|
513
522
|
|---|---|---|---|---|
|
|
514
523
|
| GET | `/api/bongos/task-visuals/:name` | `public` | — | the WIDEST path this route can take, declared honestly. |
|
|
515
524
|
|
|
516
|
-
## `tasks` (
|
|
525
|
+
## `tasks` (30)
|
|
517
526
|
|
|
518
527
|
| Method | Path | Rank | Body | Description |
|
|
519
528
|
|---|---|---|---|---|
|
|
@@ -537,6 +546,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
537
546
|
| POST | `/api/bongos/tasks/:id/promote` | `metic+archon` | — | backlog→ready promotion; scope mutation; ADR 0090 (was archon). |
|
|
538
547
|
| POST | `/api/bongos/tasks/:id/publish-branch` | `any-builder` | `branch`, `head_sha`, `bundle_b64`, `value_summary` | |
|
|
539
548
|
| GET | `/api/bongos/tasks/:id/publish-status` | `any-builder` | — | GET /tasks/:id/publish-status?branch=<branch> — poll the PR + prod-deploy state for a server-mediated publish so ship.js can confirm the … |
|
|
549
|
+
| GET | `/api/bongos/tasks/:id/recommendations` | `any-builder` | — | The per-task audit read: who recommended this task, to whom, and why. |
|
|
540
550
|
| POST | `/api/bongos/tasks/:id/ship` | `any-builder` | validated | confirmed → shipped. |
|
|
541
551
|
| POST | `/api/bongos/tasks/:id/visual` | `any-builder` | `image_b64`, `content_type`, `alt` | POST /tasks/:id/visual — attach (or replace) this task's ship-time visual. |
|
|
542
552
|
| DELETE | `/api/bongos/tasks/:id/visual` | `any-builder` | — | DELETE /tasks/:id/visual — remove it again. |
|
|
@@ -121,7 +121,34 @@ function helpRequestsNeed(ctx) {
|
|
|
121
121
|
|
|
122
122
|
// The registry. To add a future input the system needs from a builder, append a
|
|
123
123
|
// pure (ctx) => need|null function here — every renderer picks it up for free.
|
|
124
|
-
|
|
124
|
+
// Open task RECOMMENDATIONS waiting on this builder (task 1003173 / C03, goal
|
|
125
|
+
// 1000075; criterion collab-pass-a-task). Sibling of helpRequestsNeed above, and
|
|
126
|
+
// here for the same reason C01's grader round put that one here: a recommendation
|
|
127
|
+
// nobody surfaces is merely stored, and the criterion's whole argument is that a
|
|
128
|
+
// recommendation must be a visible, traceable judgement rather than a task
|
|
129
|
+
// quietly changing hands.
|
|
130
|
+
//
|
|
131
|
+
// 'info', NOT a demand, and the message says so. A recommendation grants the
|
|
132
|
+
// recipient nothing and obliges them to nothing — they still claim it themselves
|
|
133
|
+
// if they want it, and declining costs nothing (ADR 0187 §3). Wording that
|
|
134
|
+
// pressured the reader would BE the penalty the decision forbids.
|
|
135
|
+
function taskRecommendationsNeed(ctx) {
|
|
136
|
+
const n = Number(ctx.openTaskRecommendations) || 0;
|
|
137
|
+
if (n <= 0) return null;
|
|
138
|
+
const subject = n === 1 ? 'A task' : `${n} tasks`;
|
|
139
|
+
const verb = n === 1 ? 'was' : 'were';
|
|
140
|
+
return {
|
|
141
|
+
id: 'task_recommendations',
|
|
142
|
+
title: n === 1 ? 'Someone recommended a task to you' : 'Tasks were recommended to you',
|
|
143
|
+
what: 'Another builder thought you should look at a task, and said why. It is a suggestion, not an assignment.',
|
|
144
|
+
state: 'action_needed',
|
|
145
|
+
severity: 'info',
|
|
146
|
+
message: `${subject} ${verb} recommended to you. Nothing has been assigned — you would still claim it yourself, and declining costs nothing.`,
|
|
147
|
+
action: { label: n === 1 ? 'See the recommendation' : 'See the recommendations', href: '/#/collab' },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const NEEDS = [artKeyNeed, helpRequestsNeed, taskRecommendationsNeed];
|
|
125
152
|
|
|
126
153
|
// ---- founding grace (task 1002739 / direction §5.6, idea 1000701) -----------
|
|
127
154
|
//
|
|
@@ -199,7 +226,7 @@ module.exports = {
|
|
|
199
226
|
computeNeeds,
|
|
200
227
|
// exported for tests / direct reuse
|
|
201
228
|
artKeyNeed,
|
|
202
|
-
helpRequestsNeed,
|
|
229
|
+
helpRequestsNeed, taskRecommendationsNeed,
|
|
203
230
|
foundingGraceActive,
|
|
204
231
|
FOUNDING_GRACE_DAYS,
|
|
205
232
|
SETTINGS_PATH,
|
|
@@ -715,6 +715,16 @@ function createClient(opts = {}) {
|
|
|
715
715
|
// POST /sso/token — rank: public — POST /sso/token
|
|
716
716
|
postSsoToken: (args) => request("POST", "/sso/token", { hasBody: true }, args),
|
|
717
717
|
},
|
|
718
|
+
"taskRecommendations": {
|
|
719
|
+
// POST /task-recommendations — rank: any-builder — POST /task-recommendations
|
|
720
|
+
postTaskRecommendations: (args) => request("POST", "/task-recommendations", { hasBody: true }, args),
|
|
721
|
+
// PATCH /task-recommendations/{id} — rank: any-builder — PATCH /task-recommendations/:id
|
|
722
|
+
patchTaskRecommendationsId: (args) => request("PATCH", "/task-recommendations/{id}", { hasBody: true }, args),
|
|
723
|
+
// GET /task-recommendations/for-me — rank: any-builder — GET /task-recommendations/for-me
|
|
724
|
+
getTaskRecommendationsForMe: (args) => request("GET", "/task-recommendations/for-me", { hasBody: false }, args),
|
|
725
|
+
// GET /task-recommendations/mine — rank: any-builder — GET /task-recommendations/mine
|
|
726
|
+
getTaskRecommendationsMine: (args) => request("GET", "/task-recommendations/mine", { hasBody: false }, args),
|
|
727
|
+
},
|
|
718
728
|
"taskVisuals": {
|
|
719
729
|
// GET /task-visuals/{name} — rank: public — GET /task-visuals/:name
|
|
720
730
|
getTaskVisualsName: (args) => request("GET", "/task-visuals/{name}", { hasBody: false }, args),
|
|
@@ -760,6 +770,8 @@ function createClient(opts = {}) {
|
|
|
760
770
|
postTasksIdPublishBranch: (args) => request("POST", "/tasks/{id}/publish-branch", { hasBody: true }, args),
|
|
761
771
|
// GET /tasks/{id}/publish-status — rank: any-builder — GET /tasks/:id/publish-status
|
|
762
772
|
getTasksIdPublishStatus: (args) => request("GET", "/tasks/{id}/publish-status", { hasBody: false }, args),
|
|
773
|
+
// GET /tasks/{id}/recommendations — rank: any-builder — GET /tasks/:id/recommendations
|
|
774
|
+
getTasksIdRecommendations: (args) => request("GET", "/tasks/{id}/recommendations", { hasBody: false }, args),
|
|
763
775
|
// POST /tasks/{id}/ship — rank: any-builder — POST /tasks/:id/ship
|
|
764
776
|
postTasksIdShip: (args) => request("POST", "/tasks/{id}/ship", { hasBody: true }, args),
|
|
765
777
|
// DELETE /tasks/{id}/visual — rank: any-builder — DELETE /tasks/:id/visual
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const db = require('./db');
|
|
20
20
|
const classifier = require('./task-classifier');
|
|
21
21
|
const helpRequests = require('./help-requests');
|
|
22
|
+
const taskRecommendations = require('./task-recommendations');
|
|
22
23
|
|
|
23
24
|
module.exports = {
|
|
24
25
|
// --- retired doorway leaks (ADR 0093 §3) ---------------------------------
|
|
@@ -39,6 +40,10 @@ module.exports = {
|
|
|
39
40
|
// task 1003171 (C01): the count of open help requests addressed to a builder,
|
|
40
41
|
// so the kernel's /me can put it in the needs ctx WITHOUT importing this module.
|
|
41
42
|
openHelpRequestsFor: (...a) => helpRequests.countOpenForBuilder(...a),
|
|
43
|
+
// task 1003173 (C03): the count of open task recommendations waiting on a
|
|
44
|
+
// builder, exposed the same way and for the same reason — a recommendation
|
|
45
|
+
// nobody surfaces is merely stored.
|
|
46
|
+
openTaskRecommendationsFor: (...a) => taskRecommendations.countOpenForRecipient(...a),
|
|
42
47
|
getTask: (...a) => db.getTask(...a),
|
|
43
48
|
listVersions: (...a) => db.listVersions(...a),
|
|
44
49
|
versionProgress: (...a) => db.versionProgress(...a),
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
-- lifecycle_005_task_recommendations.sql — "you should look at this": recommending
|
|
2
|
+
-- a task to someone, which is NOT handing it to them (task 1003173 / C03,
|
|
3
|
+
-- goal 1000075; criterion collab-pass-a-task, ADR 0187 §3).
|
|
4
|
+
--
|
|
5
|
+
-- THE DECISION THIS TABLE ENCODES. Passing a task RECOMMENDS it; it never
|
|
6
|
+
-- transfers the claim. The recipient still claims it themselves, so claim/release
|
|
7
|
+
-- stays the single path to holding a task and nobody acquires work without opting
|
|
8
|
+
-- in. There is deliberately NO transfer column, NO 'accepted' status that grants
|
|
9
|
+
-- anything, and no seam for a future transfer: the decision was recommend-only,
|
|
10
|
+
-- and an unused seam is a cost that later reads as an intention.
|
|
11
|
+
--
|
|
12
|
+
-- WHY THE REASON IS NOT NULLABLE. The criterion's own argument is that a
|
|
13
|
+
-- recommendation which leaves no trace is indistinguishable from a task quietly
|
|
14
|
+
-- changing hands. A reason the sender must write is what makes the ledger a
|
|
15
|
+
-- record of a judgement rather than a nudge — so it is NOT NULL with a non-blank
|
|
16
|
+
-- CHECK, the same shape lifecycle_help_requests gives what_is_stuck.
|
|
17
|
+
--
|
|
18
|
+
-- DECLINING COSTS NOTHING, and that is why 'declined' is just a status here and
|
|
19
|
+
-- nothing else in the schema references it. There is no counter, no cooldown, no
|
|
20
|
+
-- penalty column on either party, and no karma or credit link — a table that
|
|
21
|
+
-- recorded a decline anywhere but here would be the penalty the criterion forbids.
|
|
22
|
+
--
|
|
23
|
+
-- MODULE-OWNED and `lifecycle_`-prefixed, so fitness check 11 permits it: a NEW
|
|
24
|
+
-- table the lifecycle module owns outright, unlike `tasks`/`builders` which are
|
|
25
|
+
-- core tables it only references (ADR 0083 §Decision #5). Mirrors
|
|
26
|
+
-- lifecycle_004_help_requests, its sibling under this goal.
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS lifecycle_task_recommendations (
|
|
29
|
+
id bigserial PRIMARY KEY,
|
|
30
|
+
|
|
31
|
+
task_id bigint NOT NULL REFERENCES tasks(id),
|
|
32
|
+
recommended_by bigint NOT NULL REFERENCES builders(id),
|
|
33
|
+
recommended_to bigint NOT NULL REFERENCES builders(id),
|
|
34
|
+
|
|
35
|
+
-- Required, per the criterion. See the header: this column IS the trace.
|
|
36
|
+
reason text NOT NULL,
|
|
37
|
+
|
|
38
|
+
-- 'open' — sent, not yet acted on
|
|
39
|
+
-- 'declined' — the recipient said no. Terminal, and carries no consequence.
|
|
40
|
+
-- 'withdrawn' — the sender took it back. Also terminal.
|
|
41
|
+
--
|
|
42
|
+
-- There is NO 'accepted'. Accepting a recommendation is CLAIMING THE TASK, which
|
|
43
|
+
-- is the claims table's job and the recipient's own action; a status here saying
|
|
44
|
+
-- "accepted" would be a second, weaker record of holding work and would make this
|
|
45
|
+
-- table look like the place claims come from. Whether the recipient went on to
|
|
46
|
+
-- claim it is answered by joining tasks/claims, not by a column here.
|
|
47
|
+
status text NOT NULL DEFAULT 'open',
|
|
48
|
+
|
|
49
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
50
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
51
|
+
|
|
52
|
+
CONSTRAINT lifecycle_task_recommendations_reason_chk
|
|
53
|
+
CHECK (length(btrim(reason)) > 0),
|
|
54
|
+
|
|
55
|
+
CONSTRAINT lifecycle_task_recommendations_status_chk
|
|
56
|
+
CHECK (status IN ('open', 'declined', 'withdrawn')),
|
|
57
|
+
|
|
58
|
+
-- Recommending a task to yourself is not a recommendation.
|
|
59
|
+
CONSTRAINT lifecycle_task_recommendations_not_self_chk
|
|
60
|
+
CHECK (recommended_by <> recommended_to)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
-- One OPEN recommendation of a given task to a given person. A second send is the
|
|
64
|
+
-- same judgement repeated, and two identical open rows would make the recipient's
|
|
65
|
+
-- list lie about how many distinct things are waiting on them. Partial, so the
|
|
66
|
+
-- same task CAN be recommended again after a decline or a withdrawal — the
|
|
67
|
+
-- decision was that declining costs nothing, and a permanent unique key would
|
|
68
|
+
-- quietly make a decline final.
|
|
69
|
+
CREATE UNIQUE INDEX IF NOT EXISTS lifecycle_task_recommendations_open_uniq
|
|
70
|
+
ON lifecycle_task_recommendations (task_id, recommended_to) WHERE status = 'open';
|
|
71
|
+
|
|
72
|
+
-- The recipient's read: "what has been recommended to me". Partial — a settled
|
|
73
|
+
-- recommendation is history and never needs routing again (the 004 pattern).
|
|
74
|
+
CREATE INDEX IF NOT EXISTS lifecycle_task_recommendations_open_to_idx
|
|
75
|
+
ON lifecycle_task_recommendations (recommended_to) WHERE status = 'open';
|
|
76
|
+
|
|
77
|
+
-- The sender's read, and the audit read the criterion asks for: the recommendation
|
|
78
|
+
-- and its author are readable AFTERWARDS, so this one is not filtered by status.
|
|
79
|
+
CREATE INDEX IF NOT EXISTS lifecycle_task_recommendations_by_idx
|
|
80
|
+
ON lifecycle_task_recommendations (recommended_by, created_at DESC);
|
|
81
|
+
|
|
82
|
+
-- "Who recommended this task, and why" — the per-task history.
|
|
83
|
+
CREATE INDEX IF NOT EXISTS lifecycle_task_recommendations_task_idx
|
|
84
|
+
ON lifecycle_task_recommendations (task_id, created_at DESC);
|
|
85
|
+
|
|
86
|
+
COMMENT ON TABLE lifecycle_task_recommendations IS
|
|
87
|
+
'A recommendation that someone look at a task (task 1003173). It NEVER transfers a claim: there is no accepted status and no transfer column, because claiming remains the recipient''s own action.';
|
|
88
|
+
|
|
89
|
+
INSERT INTO schema_migrations (version) VALUES ('lifecycle_005_task_recommendations')
|
|
90
|
+
ON CONFLICT (version) DO NOTHING;
|