@bongos/core 1.20.37 → 1.20.39

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.
Files changed (68) hide show
  1. package/.bongos-core.json +119 -64
  2. package/.claude/skills/backlog-review/SKILL.md +1 -1
  3. package/.claude/skills/bongos-feedback/SKILL.md +66 -0
  4. package/.claude/skills/collab-review/SKILL.md +1 -1
  5. package/.claude/skills/priority-session/SKILL.md +1 -1
  6. package/.claude/skills/session-handoff/SKILL.md +1 -1
  7. package/.claude/skills/status/SKILL.md +1 -1
  8. package/bin/bongos.js +1 -0
  9. package/clients/bongos-client/README.md +1 -1
  10. package/clients/bongos-client/bongos-client.global.js +8 -0
  11. package/clients/bongos-client/index.cjs +8 -0
  12. package/clients/bongos-client/index.d.ts +12 -1
  13. package/clients/bongos-client/index.mjs +8 -0
  14. package/docs/adr/0136-update-channel-subscription-policy.md +1 -1
  15. package/docs/adr/0357-the-update-rule-set-on-deploy-is-the-one-the-sweep-follows.md +41 -0
  16. package/docs/adr/README.md +1 -0
  17. package/docs/api/openapi.json +188 -2
  18. package/docs/api-reference.md +12 -4
  19. package/docs/copy-inventory.md +10 -10
  20. package/docs/copy-registry.json +18 -18
  21. package/docs/file-map.md +1 -0
  22. package/docs/module-api-changelog.md +4 -0
  23. package/docs/page-readings.json +253 -250
  24. package/docs/recipes/upgrading-the-core.md +2 -0
  25. package/modules/autonomy/db.js +39 -8
  26. package/modules/autonomy/fence.js +26 -4
  27. package/modules/autonomy/migrations/autonomy_003_priority_goal.sql +32 -0
  28. package/modules/autonomy/routes/autonomy.js +28 -1
  29. package/modules/hall-ui/public/gate.js +33 -4
  30. package/modules/ideas/routing.js +8 -1
  31. package/modules/npm-release/preview/childlog.js +95 -0
  32. package/modules/npm-release/preview/commands.js +46 -5
  33. package/modules/npm-release/preview/env.js +18 -7
  34. package/modules/npm-release/preview/runtime.js +43 -9
  35. package/modules/npm-release/preview/supervisor.js +170 -31
  36. package/modules/npm-release/public/work.css +3 -0
  37. package/modules/npm-release/public/work.js +6 -1
  38. package/modules/npm-release/routes/preview.js +1 -0
  39. package/modules/platform-identity/instance-feedback.js +142 -0
  40. package/modules/platform-identity/module.json +1 -1
  41. package/modules/platform-identity/routes/sso.js +62 -0
  42. package/modules/provisioning/render-standup.js +13 -1
  43. package/modules/public-landing/public/projects.html +22 -8
  44. package/package-lock.json +2 -2
  45. package/package.json +1 -1
  46. package/release-notes.json +30 -0
  47. package/scripts/gds/autobongos-run.js +14 -4
  48. package/scripts/gds/feedback-send.js +131 -0
  49. package/scripts/gds/provision-render.js +13 -1
  50. package/scripts/gds/render-payload.js +6 -5
  51. package/scripts/gds/update-channel.js +82 -0
  52. package/src/bongos/routes/feedback.js +110 -0
  53. package/src/bongos/routes.js +2 -0
  54. package/src/module-api.js +1 -1
  55. package/tests/autobongos_fence.mjs +62 -0
  56. package/tests/autobongos_loop.mjs +76 -0
  57. package/tests/autonomy_fence_priority.mjs +211 -0
  58. package/tests/bongos_cli.mjs +7 -1
  59. package/tests/filing_abuse_matrix.mjs +7 -2
  60. package/tests/idea_routing.mjs +1 -1
  61. package/tests/instance_feedback.mjs +327 -0
  62. package/tests/npm_release_preview_commands.mjs +1 -1
  63. package/tests/npm_release_preview_diagnosis.mjs +376 -0
  64. package/tests/npm_release_preview_supervisor.mjs +4 -4
  65. package/tests/projects_hub_app_step.mjs +49 -0
  66. package/tests/projects_hub_render_connect.mjs +2 -1
  67. package/tests/provision_render.mjs +23 -2
  68. package/tests/update_channel_db.mjs +124 -0
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: bongos-feedback
3
+ description: >-
4
+ File a Bongos problem with its maintainers — unasked, whenever a builder vents that Bongos itself is broken, stuck or confusing. Triggers: "/bongos-feedback", "Bongos is broken", "this keeps failing", "why does claim hang", "report this Bongos bug".
5
+ plain: >-
6
+ Sends a short note about a problem with Bongos, or an idea for it, to the people who build Bongos. It also does this by itself when you complain that Bongos is not working.
7
+ reach-for: >-
8
+ When Bongos itself got in your way — a command that failed or confused you, or something you wish it did. You do not have to ask; complaining is enough.
9
+ cost: >-
10
+ Free. It files one note with the Bongos maintainers and changes nothing in your project.
11
+ ---
12
+
13
+ **Script skill (authoritative).** The core action is `bongos feedback "<message>"` (or `--idea "<message>"`, or `--message-file <path>`). Run it and print its output verbatim.
14
+
15
+ This is **not** `/feedback`, which loads a walkthrough you recorded into this session. This one **sends** a note about Bongos — the tool — upstream to its maintainers on Cloud Bongos, where a bug becomes a task waiting for a maintainer's approval (tasks 1004462, 1004467).
16
+
17
+ ## Two ways in
18
+
19
+ - **Asked** — the builder says "report this", "tell the Bongos team", or runs `/bongos-feedback`. Draft it, show them, send on their go.
20
+ - **Proactive** — the builder is venting that Bongos is not working: frustration, swearing, "this is broken again", "why does X keep failing", "Bongos is useless today". **File it without being asked**, then tell them in one line (below). The owner's standing rule (2026-09-30): every rant about Bongos not working becomes something to fix. Do not ask permission first, and do not argue them out of it.
21
+
22
+ ## Is it about Bongos? (proactive mode files only when yes)
23
+
24
+ File when the complaint is about **Bongos itself**: a `bongos`/`/builder-*` command, a claim, ship, grade, merge or deploy step, the hall, Settings, sign-in, credits, the upgrade, or a Bongos message that confused them.
25
+
26
+ Do **not** file when it is about their **own project's** code, tests or design, about Claude itself, their laptop, GitHub or another service — or when the "problem" is a rule working as designed (an `[otb-plain]` card saying `By design: Yes`). If it is genuinely unclear, fix the thing in front of you first and file only if it turns out to be Bongos.
27
+
28
+ **One filing per distinct problem per session.** A second rant about the same failure adds nothing; say it is already filed (with the number) instead.
29
+
30
+ ## Writing the report
31
+
32
+ Write it yourself from what you saw. **Never paste the rant.** The maintainers need the facts, not the mood:
33
+
34
+ 1. A plain first line naming what went wrong — it becomes the title. (`bongos claim hung for two minutes, then failed with fetch failed`)
35
+ 2. What the builder was doing, what they expected, what happened instead, and the exact error line if there was one.
36
+ 3. Whether it happened more than once, and any workaround you found.
37
+
38
+ **Leave out:** code, file contents, secrets, tokens, customer or personal data, and anything about their project the maintainers do not need. Only the text you send travels (plus the Bongos and CLI versions, added automatically). As a backstop the command runs every report through Bongos's secret scrubber and refuses to send if it can't, but it is a backstop, not permission to be careless.
39
+
40
+ A report that is really a wish ("I'd like Bongos to…") goes with `--idea`.
41
+
42
+ ## Sending
43
+
44
+ From the project checkout. For anything longer than one line, write the report to a file with the Write tool first — the shell eats backticks inside a quoted argument:
45
+
46
+ ```bash
47
+ bongos feedback --message-file <path>
48
+ ```
49
+
50
+ ## Telling the builder
51
+
52
+ **Proactive:** one line, after sending, then carry on with what they were doing. For example: *"I've reported this to the Bongos team as task 1004500 — it's waiting for a maintainer to approve it."* No more than that, unless they ask what was sent — then show them the report.
53
+
54
+ **Asked:** report the CLI's output verbatim.
55
+
56
+ ## If it refuses
57
+
58
+ The CLI prints one plain line for each refusal. In proactive mode, a refusal is one line to the builder and nothing more — never retry in a loop.
59
+
60
+ - **"not connected to Cloud Bongos"** — a self-hosted project has no link to the hub. Point the builder at the Bongos core repository's issue tracker.
61
+ - **"does not yet know you as a member"** — the hub only accepts reports from someone it has seen sign in to this project. Signing in to the project once through Cloud Bongos in a browser fixes it.
62
+ - **"Too much feedback"** — the hourly cap. Don't retry; mention it once.
63
+
64
+ ## Files this skill touches
65
+
66
+ - Runs: `bongos feedback` → `scripts/gds/feedback-send.js` → `POST /api/bongos/feedback` on this project (`src/bongos/routes/feedback.js`) → the hub's `POST /api/bongos/sso/feedback` (`modules/platform-identity/instance-feedback.js`).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: collab-review
3
3
  description: >-
4
- Walk what other builders have asked of YOU — help requests and task recommendations addressed to you — verifying each against the live record before acting, and bringing back one decision at a time. The answering half of /ask-for-help. Triggers: "/collab-review", "what have people asked me", "answer my asks", "work my collab queue", or a pasted "Copy for Session Start" prompt.
4
+ Walk the help requests and task recommendations addressed to YOU, verifying each against the live record, one decision at a time. The answering half of /ask-for-help. Triggers: "/collab-review", "what have people asked me", "answer my asks", "work my collab queue", or a pasted "Copy for Session Start" prompt.
5
5
  plain: >-
6
6
  Goes through the requests other people have sent you, such as asks for help or suggested tasks, and brings you one decision at a time.
7
7
  reach-for: >-
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: priority-session
3
3
  description: >-
4
- A trusted builder answers a few plain questions; the answers reweight the idea_inbox and suggest what to claim next. Metic+ only. Triggers: "/priority-session", "run a priority session", "what should I work on next", "reprioritize the inbox", "what feels most important right now", "help me decide what to claim".
4
+ A trusted builder answers a few plain questions; the answers reweight the idea_inbox and suggest what to claim next. Metic+ only. Triggers: "/priority-session", "run a priority session", "what should I work on next", "reprioritize the inbox".
5
5
  plain: >-
6
6
  Asks you a few simple questions about what matters most, then re-ranks the ideas and suggests what to work on next.
7
7
  reach-for: >-
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: session-handoff
3
3
  description: >-
4
- Emit a paste-ready next-steps prompt so a FRESH session starts with curated context. Read-only. Triggers: "/session-handoff", "hand off to a fresh session", "give me a handoff prompt", "wrap up context for next time", "write the next-steps prompt", "I want to continue this in a new session", "context is getting heavy, prep a fresh start".
4
+ Emit a paste-ready next-steps prompt so a FRESH session starts with curated context. Read-only. Triggers: "/session-handoff", "hand off to a fresh session", "give me a handoff prompt", "wrap up context for next time", "continue this in a new session".
5
5
  plain: >-
6
6
  Writes a ready-to-paste note so a fresh conversation can pick up exactly where this one left off.
7
7
  reach-for: >-
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: status
3
3
  description: >-
4
- Roll up a version or criterion in one call — its gating tasks, their live statuses, and what is blocking. Read-only. Answers "what's the status of X" and "what's left for criterion Cn". Triggers: "/status", "what's left for C8", "status of GDS-V3", "how far along is the memory criterion", "what tasks remain to complete X".
4
+ Roll up a version or criterion in one call — its gating tasks, their live statuses, and what is blocking. Read-only. Triggers: "/status", "what's left for C8", "status of GDS-V3", "what tasks remain to complete X".
5
5
  plain: >-
6
6
  Shows how far along a version or goal is: what is finished, what is left, and what is holding it up.
7
7
  reach-for: >-
package/bin/bongos.js CHANGED
@@ -93,6 +93,7 @@ const VERBS = {
93
93
  // name, the way claim/ship/release already are (ADR 0104).
94
94
  'paste-token': { script: 'paste-token.js', group: 'account', summary: 'Finish re-auth: store the token copied from Settings (bongos paste-token <token>)' },
95
95
  doctor: { script: 'doctor.js', group: 'account', summary: 'Check your local toolchain + session' },
96
+ feedback: { script: 'feedback-send.js', group: 'account', summary: 'Send feedback about Bongos itself to its maintainers (bongos feedback "…" | --idea "…")', selfHelp: true },
96
97
 
97
98
  init: { script: 'init.js', group: 'instance', summary: 'Scaffold a new instance (config + first version + kickoff tasks)' },
98
99
  dev: { script: 'dev.js', group: 'instance', summary: 'Run THIS instance locally in one command (Postgres + migrate + server + sign-in)', selfHelp: true },
@@ -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
- - 466 operations across 70 resource groups
8
+ - 469 operations across 71 resource groups
9
9
 
10
10
  ## Use it from your project
11
11
 
@@ -177,6 +177,8 @@ function createClient(opts = {}) {
177
177
  postAutonomyFenceGoals: (args) => request("POST", "/autonomy/fence/goals", { hasBody: true }, args),
178
178
  // DELETE /autonomy/fence/goals/{goalId} — rank: archon — DELETE /autonomy/fence/goals/:goalId
179
179
  deleteAutonomyFenceGoalsGoalId: (args) => request("DELETE", "/autonomy/fence/goals/{goalId}", { hasBody: false }, args),
180
+ // POST /autonomy/fence/priority — rank: archon — POST /autonomy/fence/priority
181
+ postAutonomyFencePriority: (args) => request("POST", "/autonomy/fence/priority", { hasBody: true }, args),
180
182
  // POST /autonomy/heartbeat — rank: any-builder — POST /autonomy/heartbeat
181
183
  postAutonomyHeartbeat: (args) => request("POST", "/autonomy/heartbeat", { hasBody: true }, args),
182
184
  // GET /autonomy/precheck — rank: metic+archon — GET /autonomy/precheck
@@ -368,6 +370,10 @@ function createClient(opts = {}) {
368
370
  // GET /done-when/pending-review — rank: metic+archon — GET /done-when/pending-review
369
371
  getDoneWhenPendingReview: (args) => request("GET", "/done-when/pending-review", { hasBody: false }, args),
370
372
  },
373
+ "feedback": {
374
+ // POST /feedback — rank: any-builder — POST /feedback
375
+ postFeedback: (args) => request("POST", "/feedback", { hasBody: true }, args),
376
+ },
371
377
  "gateApprovals": {
372
378
  // GET /gate-approvals — rank: metic+archon — GET /gate-approvals
373
379
  getGateApprovals: (args) => request("GET", "/gate-approvals", { hasBody: false }, args),
@@ -1019,6 +1025,8 @@ function createClient(opts = {}) {
1019
1025
  postSsoDevicePoll: (args) => request("POST", "/sso/device/poll", { hasBody: true }, args),
1020
1026
  // POST /sso/device/start — rank: public — POST /sso/device/start
1021
1027
  postSsoDeviceStart: (args) => request("POST", "/sso/device/start", { hasBody: true }, args),
1028
+ // POST /sso/feedback — rank: public — POST /sso/feedback
1029
+ postSsoFeedback: (args) => request("POST", "/sso/feedback", { hasBody: true }, args),
1022
1030
  // POST /sso/invites/clear — rank: public — POST /sso/invites/clear
1023
1031
  postSsoInvitesClear: (args) => request("POST", "/sso/invites/clear", { hasBody: true }, args),
1024
1032
  // POST /sso/invites/notify — rank: public — POST /sso/invites/notify
@@ -176,6 +176,8 @@ function createClient(opts = {}) {
176
176
  postAutonomyFenceGoals: (args) => request("POST", "/autonomy/fence/goals", { hasBody: true }, args),
177
177
  // DELETE /autonomy/fence/goals/{goalId} — rank: archon — DELETE /autonomy/fence/goals/:goalId
178
178
  deleteAutonomyFenceGoalsGoalId: (args) => request("DELETE", "/autonomy/fence/goals/{goalId}", { hasBody: false }, args),
179
+ // POST /autonomy/fence/priority — rank: archon — POST /autonomy/fence/priority
180
+ postAutonomyFencePriority: (args) => request("POST", "/autonomy/fence/priority", { hasBody: true }, args),
179
181
  // POST /autonomy/heartbeat — rank: any-builder — POST /autonomy/heartbeat
180
182
  postAutonomyHeartbeat: (args) => request("POST", "/autonomy/heartbeat", { hasBody: true }, args),
181
183
  // GET /autonomy/precheck — rank: metic+archon — GET /autonomy/precheck
@@ -367,6 +369,10 @@ function createClient(opts = {}) {
367
369
  // GET /done-when/pending-review — rank: metic+archon — GET /done-when/pending-review
368
370
  getDoneWhenPendingReview: (args) => request("GET", "/done-when/pending-review", { hasBody: false }, args),
369
371
  },
372
+ "feedback": {
373
+ // POST /feedback — rank: any-builder — POST /feedback
374
+ postFeedback: (args) => request("POST", "/feedback", { hasBody: true }, args),
375
+ },
370
376
  "gateApprovals": {
371
377
  // GET /gate-approvals — rank: metic+archon — GET /gate-approvals
372
378
  getGateApprovals: (args) => request("GET", "/gate-approvals", { hasBody: false }, args),
@@ -1018,6 +1024,8 @@ function createClient(opts = {}) {
1018
1024
  postSsoDevicePoll: (args) => request("POST", "/sso/device/poll", { hasBody: true }, args),
1019
1025
  // POST /sso/device/start — rank: public — POST /sso/device/start
1020
1026
  postSsoDeviceStart: (args) => request("POST", "/sso/device/start", { hasBody: true }, args),
1027
+ // POST /sso/feedback — rank: public — POST /sso/feedback
1028
+ postSsoFeedback: (args) => request("POST", "/sso/feedback", { hasBody: true }, args),
1021
1029
  // POST /sso/invites/clear — rank: public — POST /sso/invites/clear
1022
1030
  postSsoInvitesClear: (args) => request("POST", "/sso/invites/clear", { hasBody: true }, args),
1023
1031
  // POST /sso/invites/notify — rank: public — POST /sso/invites/notify
@@ -251,8 +251,9 @@ export interface PostAuthRevokeRequest { session_id?: StringifiableId }
251
251
  export interface PostAuthRevokeResponse { ok: boolean; revoked: unknown }
252
252
  export interface PostAutonomyFenceGoalsRequest { goal_id: StringifiableId; note?: string }
253
253
  export interface PostAutonomyFenceGoalsResponse { goal: unknown }
254
+ export interface PostAutonomyFencePriorityRequest { goal_id?: StringifiableId }
254
255
  export interface PostAutonomyFenceRequest { enabled: boolean; reason?: string }
255
- export interface PostAutonomyHeartbeatRequest { host: string; pid?: number; started_at?: string; mode?: string; consecutive_failures?: number; last_event?: string; working_task_id?: number }
256
+ export interface PostAutonomyHeartbeatRequest { host: string; pid?: number; started_at?: string; mode?: string; consecutive_failures?: number; last_event?: string; working_task_id?: number; working_goal_id?: number }
256
257
  export interface PostAutonomyHeartbeatResponse { ok: boolean; heartbeat: unknown }
257
258
  export interface PostBackupTriggerResponse { ok: boolean; message: unknown; started_at: unknown }
258
259
  export interface PostBlockersIdLinkRequest { task_id: StringifiableId }
@@ -293,6 +294,7 @@ export interface PostDoneWhenCriterionIdUatRecordingResponse { ok: boolean; reco
293
294
  export interface PostDoneWhenCriterionIdUatRequest { kind: string; recording?: string; live_attested?: boolean; note?: string }
294
295
  export interface PostDoneWhenCriterionIdUatResponse { ok: boolean; signoff: unknown; criterion_closed: unknown; achieved_goals: unknown; closed_versions: unknown }
295
296
  export interface PostDoneWhenCriterionIdUnsatisfyResponse { criterion: unknown }
297
+ export interface PostFeedbackRequest { message: string; kind?: "bug" | "idea"; context?: unknown }
296
298
  export interface PostGateApprovalsPrApproveResponse { ok: boolean; pr: unknown; already_clear?: boolean; message: unknown; head_sha?: unknown; status_set?: boolean; recheck?: unknown; held_for_artist?: unknown }
297
299
  export interface PostGithubRepoVisibilityRequest { repo: string; visibility: "public" | "private" }
298
300
  export interface PostGithubRepoVisibilityResponse { ok: boolean; repo: unknown; private_deployable: boolean }
@@ -445,6 +447,7 @@ export interface PostSsoApplicantProfileRequest { client_id: string; client_secr
445
447
  export interface PostSsoApplicantProfileResponse { profile: unknown }
446
448
  export interface PostSsoDevicePollRequest { device_code: string }
447
449
  export interface PostSsoDeviceStartRequest { origin: string }
450
+ export interface PostSsoFeedbackRequest { client_id: string; client_secret: string; github_id: StringifiableId; message: string; kind?: string; context?: unknown }
448
451
  export interface PostSsoInvitesClearRequest { client_id: string; client_secret: string; github_login: string }
449
452
  export interface PostSsoInvitesNotifyRequest { client_id: string; client_secret: string; github_login: string }
450
453
  export interface PostSsoMembershipCheckInRequest { client_id: string; client_secret: string; github_id: StringifiableId; github_login: string; display_name?: string; avatar_url?: string; membership_kind?: string }
@@ -608,6 +611,8 @@ export interface BongosClient {
608
611
  postAutonomyFenceGoals(args: RequestArgs & { body: PostAutonomyFenceGoalsRequest }): Promise<PostAutonomyFenceGoalsResponse>;
609
612
  /** DELETE /autonomy/fence/goals/{goalId} — rank: archon */
610
613
  deleteAutonomyFenceGoalsGoalId(args?: RequestArgs): Promise<ApiResponse>;
614
+ /** POST /autonomy/fence/priority — rank: archon */
615
+ postAutonomyFencePriority(args?: RequestArgs & { body?: PostAutonomyFencePriorityRequest }): Promise<ApiResponse>;
611
616
  /** POST /autonomy/heartbeat — rank: any-builder */
612
617
  postAutonomyHeartbeat(args: RequestArgs & { body: PostAutonomyHeartbeatRequest }): Promise<PostAutonomyHeartbeatResponse>;
613
618
  /** GET /autonomy/precheck — rank: metic+archon */
@@ -799,6 +804,10 @@ export interface BongosClient {
799
804
  /** GET /done-when/pending-review — rank: metic+archon */
800
805
  getDoneWhenPendingReview(args?: RequestArgs): Promise<GetDoneWhenPendingReviewResponse>;
801
806
  };
807
+ "feedback": {
808
+ /** POST /feedback — rank: any-builder */
809
+ postFeedback(args: RequestArgs & { body: PostFeedbackRequest }): Promise<ApiResponse>;
810
+ };
802
811
  "gateApprovals": {
803
812
  /** GET /gate-approvals — rank: metic+archon */
804
813
  getGateApprovals(args?: RequestArgs): Promise<ApiResponse>;
@@ -1450,6 +1459,8 @@ export interface BongosClient {
1450
1459
  postSsoDevicePoll(args: RequestArgs & { body: PostSsoDevicePollRequest }): Promise<ApiResponse>;
1451
1460
  /** POST /sso/device/start — rank: public */
1452
1461
  postSsoDeviceStart(args: RequestArgs & { body: PostSsoDeviceStartRequest }): Promise<ApiResponse>;
1462
+ /** POST /sso/feedback — rank: public */
1463
+ postSsoFeedback(args: RequestArgs & { body: PostSsoFeedbackRequest }): Promise<ApiResponse>;
1453
1464
  /** POST /sso/invites/clear — rank: public */
1454
1465
  postSsoInvitesClear(args: RequestArgs & { body: PostSsoInvitesClearRequest }): Promise<ApiResponse>;
1455
1466
  /** POST /sso/invites/notify — rank: public */
@@ -173,6 +173,8 @@ export function createClient(opts = {}) {
173
173
  postAutonomyFenceGoals: (args) => request("POST", "/autonomy/fence/goals", { hasBody: true }, args),
174
174
  // DELETE /autonomy/fence/goals/{goalId} — rank: archon — DELETE /autonomy/fence/goals/:goalId
175
175
  deleteAutonomyFenceGoalsGoalId: (args) => request("DELETE", "/autonomy/fence/goals/{goalId}", { hasBody: false }, args),
176
+ // POST /autonomy/fence/priority — rank: archon — POST /autonomy/fence/priority
177
+ postAutonomyFencePriority: (args) => request("POST", "/autonomy/fence/priority", { hasBody: true }, args),
176
178
  // POST /autonomy/heartbeat — rank: any-builder — POST /autonomy/heartbeat
177
179
  postAutonomyHeartbeat: (args) => request("POST", "/autonomy/heartbeat", { hasBody: true }, args),
178
180
  // GET /autonomy/precheck — rank: metic+archon — GET /autonomy/precheck
@@ -364,6 +366,10 @@ export function createClient(opts = {}) {
364
366
  // GET /done-when/pending-review — rank: metic+archon — GET /done-when/pending-review
365
367
  getDoneWhenPendingReview: (args) => request("GET", "/done-when/pending-review", { hasBody: false }, args),
366
368
  },
369
+ "feedback": {
370
+ // POST /feedback — rank: any-builder — POST /feedback
371
+ postFeedback: (args) => request("POST", "/feedback", { hasBody: true }, args),
372
+ },
367
373
  "gateApprovals": {
368
374
  // GET /gate-approvals — rank: metic+archon — GET /gate-approvals
369
375
  getGateApprovals: (args) => request("GET", "/gate-approvals", { hasBody: false }, args),
@@ -1015,6 +1021,8 @@ export function createClient(opts = {}) {
1015
1021
  postSsoDevicePoll: (args) => request("POST", "/sso/device/poll", { hasBody: true }, args),
1016
1022
  // POST /sso/device/start — rank: public — POST /sso/device/start
1017
1023
  postSsoDeviceStart: (args) => request("POST", "/sso/device/start", { hasBody: true }, args),
1024
+ // POST /sso/feedback — rank: public — POST /sso/feedback
1025
+ postSsoFeedback: (args) => request("POST", "/sso/feedback", { hasBody: true }, args),
1018
1026
  // POST /sso/invites/clear — rank: public — POST /sso/invites/clear
1019
1027
  postSsoInvitesClear: (args) => request("POST", "/sso/invites/clear", { hasBody: true }, args),
1020
1028
  // POST /sso/invites/notify — rank: public — POST /sso/invites/notify
@@ -1,7 +1,7 @@
1
1
  # ADR 0136 — Update-channel subscription policy (patch-only, health-gated, opt-in, autonomy-gated)
2
2
 
3
3
  **Date:** 2026-07-10
4
- **Status:** Accepted.
4
+ **Status:** Accepted. Amended by [ADR 0357](0357-the-update-rule-set-on-deploy-is-the-one-the-sweep-follows.md) (2026-09-30): a project's channel is now its `provisioning_instances.update_channel`, set on /deploy; the roster's `channel` is only the fallback.
5
5
  **Context:** [task 2150](https://example.com/builders#/task/2150) (BONGOS-V1, goal 26). The last piece of the update-consumption workstream: [ADR 0100](0100-two-repo-instance-model.md) §2 / [task 1690](https://example.com/builders#/task/1690) gave a consumer the manual `bongos upgrade` channel; [ADR 0134](0134-private-first-npm-distribution.md) / [task 2090](https://example.com/builders#/task/2090) added the private-registry source; [task 2149](https://example.com/builders#/task/2149) added auto-rollback; [task 2157](https://example.com/builders#/task/2157) proved rollback live on the demo instance. What was missing is the **subscription** — how a consumer instance takes new releases *on a cadence, unattended*, rather than a human running `bongos upgrade` each time. Builds on the scheduled-routines portability contract ([ADR 0115](0115-scheduled-routines-portability.md)).
6
6
 
7
7
  ## Problem
@@ -0,0 +1,41 @@
1
+ # ADR 0357 — The update rule set on /deploy is the one the sweep follows
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-30
5
+ - **Task:** [task 1004468](https://cloudbongos.com/builders#/task/1004468) (goal 1000090, BONGOS-V2)
6
+ - **Amends:** [ADR 0136](0136-update-channel-subscription-policy.md), "Alternatives considered" (a `provisioning_instances` column for the channel) and its Consequences line naming `config/update-subscriptions.json` as the one place a channel is set
7
+ - **Builds on:** [task 1004445](https://cloudbongos.com/builders#/task/1004445) (migration provisioning_029, `POST /provisioning/instances/:id/update-channel`), [ADR 0293](0293-the-owners-deploy-door-is-an-intent-the-control-plane-drains.md) (the deploy door)
8
+ - **Decided with:** the owner, 2026-09-30. Asked "which one decides how far a project auto-updates, the site setting or the server file?", they chose the site setting.
9
+
10
+ ## Context
11
+
12
+ ADR 0136 put each subscribed instance's update channel (`pinned` / `patch` / `minor`) in the roster file `config/update-subscriptions.json`, and it rejected a `provisioning_instances` column because the fleet registry was not yet designed. By the time of [ADR 0325](0325-the-managed-fleet-control-plane-is-the-hub-we-already-run.md), `provisioning_instances` *was* the registry. Task 1004445 then added `update_channel` to it, so the owner could set the rule on /deploy, and the deploy door's runner obeys it.
13
+
14
+ That left two rules for one project. On 2026-09-30 cloudbongos.com was set to `minor` on /deploy and still `patch` in the box roster. The button followed the first and the unattended sweep the second, so a step-up release (1.20 → 1.21) would never land unattended even though /deploy said it would. Task 1004451's proof was graded down for exactly this.
15
+
16
+ ## Decision
17
+
18
+ **D1. The project row is the rule.** For a roster entry whose `slug` matches a `provisioning_instances` row, the sweep uses that row's `update_channel`. The match is by slug, the only stable key the table carries (it records no directory).
19
+
20
+ **D2. The roster's `channel` is a fallback, not a second vote.** It applies only when no row matches (an instance the control plane never provisioned) or when the sweep cannot read the database. The roster keeps everything else it holds — `dir`, service, health URL, topology, `env`, pin mode — because the registry doesn't record those.
21
+
22
+ **D3. Every fallback says so.** The sweep prints each entry's channel and where it came from ("set on /deploy", or "from the roster — <reason>"). When the row overrides a different roster value, the log names the ignored value too. A silent fallback is how two rules drifted apart in the first place.
23
+
24
+ **D4. It reads the control plane's database, over psql, from the sweep's own environment** — `DATABASE_URL`, else `PGDATABASE` (socket + peer auth). These are the two variables `upgrade.js` `recordLedger` reads. Unlike recordLedger, it hands a `DATABASE_URL` to `psql -d` rather than the pg driver, because the sweep is synchronous. An entry's `env` is never used, because it names *that instance's* database. A core too old to carry the reader keeps the roster behaviour unchanged. A failure is logged as a fixed class (unreachable, login refused, table missing…), never psql's own text, which can name a user or host.
25
+
26
+ **D1a. Why not slug *or* directory.** The task brief suggested matching by slug or dir. `provisioning_instances` stores no directory, so there is nothing to match a dir against, and a roster slug that differs from its row's slug falls back to the roster, visibly (D3).
27
+
28
+ **D5. Nothing is migrated or copied.** Every row already has a channel (the column defaults to `patch`, the old behaviour). So once this is live, an entry the roster listed as `minor` but whose row was never changed on /deploy is swept as `patch`. That is the ruling applied, not a regression, and D3 makes it visible in the log.
29
+
30
+ ## Consequences
31
+
32
+ - One place to change how far a project auto-updates, on the site, with no SSH.
33
+ - ADR 0136's policy is otherwise unchanged: majors and prereleases are never automatic, every move is health-gated with auto-rollback, and the routine stays opt-in and autonomy-gated. Enrollment itself (which instances the sweep touches, and where they live) is still the roster.
34
+ - **Delivery depends on the sweep running current code.** On cloudbongos.com the sweep runs from a checkout frozen at 2026-08-25 ([task 1004307](https://cloudbongos.com/builders#/task/1004307)). Until that is fixed, or the checkout is updated, a release carrying this change does not reach the sweep.
35
+ - The rule is now testable in isolation: `tests/update_channel_db.mjs`.
36
+
37
+ ## Alternatives considered
38
+
39
+ - **Keep the roster in charge (ADR 0136 as written).** Rejected by the owner: changing the unattended behaviour would stay an SSH edit, and /deploy would keep showing a setting the sweep ignores.
40
+ - **Take the stricter of the two.** Rejected: safe, but setting `minor` on the site would do nothing until someone also edited the file, which is the same confusion in a new form.
41
+ - **Copy the roster's channels into the rows first.** Rejected: the rows already hold the owner's deliberate choices (cloudbongos = `minor`), and a copy would overwrite a newer decision with an older file.
@@ -475,3 +475,4 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
475
475
  | 0354 | [**A builder hosts one project for free, and more takes the fleet permission** ([task 1004412](https://cloudbongos.com/builders#/task/1004412), goal 1000106 — working area 1, the owner's ruling of 2026-09-30). The create wizard always asks for `cloud-host`, which task 1003370 had gated behind `provisioning.fleet.manage`, so every new (Xenos) user was refused at "Create my project" — against ADR 0345 decision 1. **D1:** a builder's first live `cloud-host` project needs no permission; a further one needs the fleet permission (a retry of your own slug is the same project; only `torn_down` frees the place; an unreadable count refuses). **D2:** one first-free request per builder at a time (409 `create_in_progress`), held as a Postgres advisory lock so it holds across processes. **D3:** the wizard states `permission_forbidden` and `box_full` in plain words. Nothing yet caps a hosted project's memory.](0354-a-builder-hosts-one-project-free-and-more-takes-the-fleet-permission.md) | provisioning / security / project creation |
476
476
  | 0355 | [**A soft limit is a rule with a reason, and nobody enforces it** ([task 1004416](https://cloudbongos.com/builders#/task/1004416), goal 1000121 — BV2.PS02). The startup flow's seven planet-physics answers and what they rule out. **D1:** the answers are more provisioning `detail` keys under `team_shape`'s contract (no defaults, refused loudly, `null` un-answers), with two new kinds — a list (`many`) and a whole number (`count`); the country is a closed ISO list. **D2:** answers must agree only within one request, so a merge patch's edit order never matters. **D3:** one declarative soft-limit table (`soft-limits.js`) — data conditions, `hide`/`grey`/`note`, a reason on every rule — and every resolution says `enforced: false`. **D4:** screening is a seam (`screening.js`) with a deliberately small rule set; a stop is 422 `screened_out` before anything exists. **D5:** the edit route screens a new description too.](0355-a-soft-limit-is-a-rule-with-a-reason-and-nobody-enforces-it.md) | provisioning / project startup |
477
477
  | 0356 | [**A recruit invite expires 30 days after it is sent, decided at read time** ([task 1002969](https://cloudbongos.com/builders#/task/1002969), goal 1000110 — privacy spec D4, owner-signed). **D1:** one definition, `src/bongos/invite-expiry.js` (`INVITE_EXPIRY_DAYS` + the SQL), read by the sign-in gate, the waiting-page probe, the onboarding routes and, through the doorway's `inviteExpiry`, the hub. Nothing is stored and nothing sweeps. **D2:** only `kind='invite'` rows expire; approved applications and goal invites do not. **D3:** amends ADR 0335 D2.5: the gate reads `kind` only inside the expiry unit, which can close a door and never open one. **D4:** an expired invite is refused, marked `expired` + `expires_at` in the queues, reported `expired` by the status probe (the CLI stops), and replaced by a re-invite's fresh row. **D5:** the hub hides a `pending` notice past 30 days and a re-invite restamps it. Split out: `seen_at` (task 1004443), the decline relay (task 1004444).](0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md) | platform identity / admission / invites |
478
+ | 0357 | [**The update rule set on /deploy is the one the sweep follows** ([task 1004468](https://cloudbongos.com/builders#/task/1004468), goal 1000090 — BONGOS-V2, owner's ruling 2026-09-30). Amends [ADR 0136](0136-update-channel-subscription-policy.md)'s rejected DB-column alternative. **D1:** for a roster entry whose slug matches a `provisioning_instances` row, the unattended sweep uses that row's `update_channel` (set on /deploy, task 1004445). **D2:** the roster's `channel` is only the fallback — no matching row, or the database unreadable; enrollment and topology stay in the roster. **D3:** the sweep logs each entry's channel and its source, naming an overridden roster value. **D4:** read over psql from the sweep's own `DATABASE_URL`/`PGDATABASE`, never an entry's `env`. **D5:** nothing is copied; a row never changed is `patch`. Delivery to cloudbongos.com waits on the frozen sweep checkout (task 1004307). Rejected: roster stays in charge; stricter-of-the-two; copying roster values into rows.](0357-the-update-rule-set-on-deploy-is-the-one-the-sweep-follows.md) | distribution / updates |
@@ -121,6 +121,10 @@
121
121
  "name": "done-when",
122
122
  "description": "Endpoints under `/api/bongos/done-when`."
123
123
  },
124
+ {
125
+ "name": "feedback",
126
+ "description": "Endpoints under `/api/bongos/feedback`."
127
+ },
124
128
  {
125
129
  "name": "gate-approvals",
126
130
  "description": "Endpoints under `/api/bongos/gate-approvals`."
@@ -1890,6 +1894,51 @@
1890
1894
  ]
1891
1895
  }
1892
1896
  },
1897
+ "/autonomy/fence/priority": {
1898
+ "post": {
1899
+ "operationId": "post_autonomy_fence_priority",
1900
+ "tags": [
1901
+ "autonomy"
1902
+ ],
1903
+ "summary": "POST /autonomy/fence/priority",
1904
+ "description": "The priority goal (task 1004453): which allowlisted goal the runner works first. Same gate as the allowlist — choosing what an unattended machine does first is part of choosing what it may do. The goal must already be on the allowlist (409 otherwise), so a priority can order the list and never widen it. `goal_id: null` clears it.\n\n**Rank:** `archon` — Archon only (rank and identity management + the escalation keys — the trust boundary).\n\n**Permissions:** `autonomy.fence.manage` (all required).",
1905
+ "x-rank": "archon",
1906
+ "x-source": "modules/autonomy/routes/autonomy.js",
1907
+ "x-permissions": [
1908
+ "autonomy.fence.manage"
1909
+ ],
1910
+ "requestBody": {
1911
+ "required": false,
1912
+ "content": {
1913
+ "application/json": {
1914
+ "schema": {
1915
+ "$ref": "#/components/schemas/PostAutonomyFencePriorityRequest"
1916
+ }
1917
+ }
1918
+ },
1919
+ "x-validated": true
1920
+ },
1921
+ "responses": {
1922
+ "200": {
1923
+ "description": "Success."
1924
+ },
1925
+ "400": {
1926
+ "$ref": "#/components/responses/ValidationFailed"
1927
+ },
1928
+ "401": {
1929
+ "$ref": "#/components/responses/Unauthorized"
1930
+ },
1931
+ "403": {
1932
+ "$ref": "#/components/responses/Forbidden"
1933
+ }
1934
+ },
1935
+ "security": [
1936
+ {
1937
+ "builderSession": []
1938
+ }
1939
+ ]
1940
+ }
1941
+ },
1893
1942
  "/autonomy/heartbeat": {
1894
1943
  "post": {
1895
1944
  "operationId": "post_autonomy_heartbeat",
@@ -5637,6 +5686,48 @@
5637
5686
  ]
5638
5687
  }
5639
5688
  },
5689
+ "/feedback": {
5690
+ "post": {
5691
+ "operationId": "post_feedback",
5692
+ "tags": [
5693
+ "feedback"
5694
+ ],
5695
+ "summary": "POST /feedback",
5696
+ "description": "signed-in builder — reporting a problem with Bongos grants nothing here, and the hub files every report at 'backlog' behind a Metic+ nod.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
5697
+ "x-rank": "any-builder",
5698
+ "x-source": "src/bongos/routes/feedback.js",
5699
+ "requestBody": {
5700
+ "required": true,
5701
+ "content": {
5702
+ "application/json": {
5703
+ "schema": {
5704
+ "$ref": "#/components/schemas/PostFeedbackRequest"
5705
+ }
5706
+ }
5707
+ },
5708
+ "x-validated": true
5709
+ },
5710
+ "responses": {
5711
+ "200": {
5712
+ "description": "Success."
5713
+ },
5714
+ "400": {
5715
+ "$ref": "#/components/responses/ValidationFailed"
5716
+ },
5717
+ "401": {
5718
+ "$ref": "#/components/responses/Unauthorized"
5719
+ },
5720
+ "403": {
5721
+ "$ref": "#/components/responses/Forbidden"
5722
+ }
5723
+ },
5724
+ "security": [
5725
+ {
5726
+ "builderSession": []
5727
+ }
5728
+ ]
5729
+ }
5730
+ },
5640
5731
  "/gate-approvals": {
5641
5732
  "get": {
5642
5733
  "operationId": "get_gate_approvals",
@@ -18773,6 +18864,38 @@
18773
18864
  "security": []
18774
18865
  }
18775
18866
  },
18867
+ "/sso/feedback": {
18868
+ "post": {
18869
+ "operationId": "post_sso_feedback",
18870
+ "tags": [
18871
+ "sso"
18872
+ ],
18873
+ "summary": "POST /sso/feedback",
18874
+ "description": "POST /sso/feedback — a federated project relays one builder's feedback about Bongos itself (task 1004462). A bug becomes a 'backlog' task in the building version's maintenance goal; an idea becomes an inbox row. Why always backlog, and why the sender must be a witnessed member: ../instance-feedback.js. rank: public — authenticated by client_id + client_secret, not a builder rank. The per-project and per-sender caps run after the client check, so an unauthenticated caller cannot spend a real project's allowance.\n\n**Rank:** `public` — No authentication — any caller.",
18875
+ "x-rank": "public",
18876
+ "x-source": "modules/platform-identity/routes/sso.js",
18877
+ "requestBody": {
18878
+ "required": true,
18879
+ "content": {
18880
+ "application/json": {
18881
+ "schema": {
18882
+ "$ref": "#/components/schemas/PostSsoFeedbackRequest"
18883
+ }
18884
+ }
18885
+ },
18886
+ "x-validated": true
18887
+ },
18888
+ "responses": {
18889
+ "200": {
18890
+ "description": "Success."
18891
+ },
18892
+ "400": {
18893
+ "$ref": "#/components/responses/ValidationFailed"
18894
+ }
18895
+ },
18896
+ "security": []
18897
+ }
18898
+ },
18776
18899
  "/sso/invites/clear": {
18777
18900
  "post": {
18778
18901
  "operationId": "post_sso_invites_clear",
@@ -25188,6 +25311,15 @@
25188
25311
  "goal"
25189
25312
  ]
25190
25313
  },
25314
+ "PostAutonomyFencePriorityRequest": {
25315
+ "type": "object",
25316
+ "properties": {
25317
+ "goal_id": {
25318
+ "$ref": "#/components/schemas/StringifiableId"
25319
+ }
25320
+ },
25321
+ "additionalProperties": false
25322
+ },
25191
25323
  "PostAutonomyFenceRequest": {
25192
25324
  "type": "object",
25193
25325
  "properties": {
@@ -25232,6 +25364,9 @@
25232
25364
  },
25233
25365
  "working_task_id": {
25234
25366
  "type": "number"
25367
+ },
25368
+ "working_goal_id": {
25369
+ "type": "number"
25235
25370
  }
25236
25371
  },
25237
25372
  "required": [
@@ -25977,6 +26112,27 @@
25977
26112
  "criterion"
25978
26113
  ]
25979
26114
  },
26115
+ "PostFeedbackRequest": {
26116
+ "type": "object",
26117
+ "properties": {
26118
+ "message": {
26119
+ "type": "string",
26120
+ "maxLength": 4000
26121
+ },
26122
+ "kind": {
26123
+ "type": "string",
26124
+ "enum": [
26125
+ "bug",
26126
+ "idea"
26127
+ ]
26128
+ },
26129
+ "context": {}
26130
+ },
26131
+ "required": [
26132
+ "message"
26133
+ ],
26134
+ "additionalProperties": false
26135
+ },
25980
26136
  "PostGateApprovalsPrApproveResponse": {
25981
26137
  "type": "object",
25982
26138
  "properties": {
@@ -28652,6 +28808,36 @@
28652
28808
  ],
28653
28809
  "additionalProperties": false
28654
28810
  },
28811
+ "PostSsoFeedbackRequest": {
28812
+ "type": "object",
28813
+ "properties": {
28814
+ "client_id": {
28815
+ "type": "string",
28816
+ "maxLength": 200
28817
+ },
28818
+ "client_secret": {
28819
+ "type": "string",
28820
+ "maxLength": 512
28821
+ },
28822
+ "github_id": {
28823
+ "$ref": "#/components/schemas/StringifiableId"
28824
+ },
28825
+ "message": {
28826
+ "type": "string"
28827
+ },
28828
+ "kind": {
28829
+ "type": "string"
28830
+ },
28831
+ "context": {}
28832
+ },
28833
+ "required": [
28834
+ "client_id",
28835
+ "client_secret",
28836
+ "github_id",
28837
+ "message"
28838
+ ],
28839
+ "additionalProperties": false
28840
+ },
28655
28841
  "PostSsoInvitesClearRequest": {
28656
28842
  "type": "object",
28657
28843
  "properties": {
@@ -29812,8 +29998,8 @@
29812
29998
  "description": "A required dependency/feature is not configured or is temporarily down."
29813
29999
  }
29814
30000
  },
29815
- "x-endpoint-count": 466,
29816
- "x-schema-count": 498,
30001
+ "x-endpoint-count": 469,
30002
+ "x-schema-count": 501,
29817
30003
  "x-undocumented-bodies": 12,
29818
30004
  "x-response-schemas": 337,
29819
30005
  "x-generated-by": "scripts/gds/gen-api-docs.js"