From 5b368fca25e46852e936267e3377f2f5082f61c9 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 27 May 2026 16:31:16 -0700 Subject: [PATCH 1/9] =?UTF-8?q?spec(api-gaps):=20BC5=20launch=20reconcilia?= =?UTF-8?q?tion=20=E2=80=94=20memories=20regression=20+=20scope=20cuts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Post-launch (2026-05-27) reconciliation of the API-gap registry to what BC5 master actually shipped vs BC4 (the four branch). - New spec/api-gaps/memories-emptied-regression.md: the confirmed live subtractive regression (BC5 master ships json.memories [] while BC4 four populates it). Not an additive gap; status partial-coverage (closest fit). The fix is written but unmerged in BC3 #10947 (json.memories @bubble_ups); status flips to addressed-in-bc3-pr-10947 on merge. bc3_pr: 10947 set. - stack-doc-and-smithy.md: flipped partial-coverage -> confirmed-not-api-resource. Stacks are web-only on both four and master (layout false, no respond_to :json, no doc section; Stacks::CollectablesController absent on master; the _stack partial only renders nested). No StacksService. Retained as the classification record per the briefs-trump-allowlist rule. - everything-aggregates.md: 22 -> 17 endpoints. The files group was dropped (master consolidated file-type routes into /files/recent?kind= and unions unrenderable attachment recordings). 8 active groups; explicit route list. Files-by-kind noted as a possible future deliverable, not modeled now. - activity-timeline.md: FYI that /activity.json + /projects/:id/timeline.json ship on master directly (not PR #10947); the removed /activity/days + /activity/dates sub-routes were never modeled. - README.md: stack row -> confirmed-not-api-resource; new memories-emptied-regression row; note distinguishing the subtractive regression from the registry's additive gaps. - COORDINATION.md: both contract-decision items reframed as open-pending-merge, not open-pending-decision. The counterfactual "commit 64acf34 aliases memories to bubble_ups" claim is purged (no such commit on four/five/master). BC3 provenance at the finding: five+api @ 716e710ee5 (reconciliation handoff). make validate-api-gaps: clean (12 entries). No spec/generated changes — the smithy doc comment is left as-is (already substantively accurate) so this stays a docs/registry-only change with no cross-language regen. --- COORDINATION.md | 31 ++++--- spec/api-gaps/README.md | 9 +- spec/api-gaps/activity-timeline.md | 6 ++ spec/api-gaps/everything-aggregates.md | 63 ++++++++------ spec/api-gaps/memories-emptied-regression.md | 78 +++++++++++++++++ spec/api-gaps/stack-doc-and-smithy.md | 89 +++++++++----------- 6 files changed, 189 insertions(+), 87 deletions(-) create mode 100644 spec/api-gaps/memories-emptied-regression.md diff --git a/COORDINATION.md b/COORDINATION.md index bf4cca352..055b681f5 100644 --- a/COORDINATION.md +++ b/COORDINATION.md @@ -29,20 +29,27 @@ in that branch under `tmp/api_audit.md` (generated by the BC3 plan's ## Contract decisions (cross-team) -Two items the SDK canary surfaced that need product/API leadership decisions: - -1. `memories` going to `[]` on `GET /my/readings.json` — back-compat or - accept-and-document. BC4 populates the array; BC5 emits literal `[]`. - Existing BC4 API users reading `memories` would silently see no data on - BC5 — a subtractive change disguised as compatible. +Two items the SDK canary surfaced. As of the BC5 launch (2026-05-27) both +remain **open** — an earlier draft of this file framed them as pending a +*decision* when they are actually pending a *merge*: + +1. `memories` going to `[]` on `GET /my/readings.json` — **confirmed live on + BC5 `master`** (source-diffed against `four`, which still populates it). + Filed as + [`spec/api-gaps/memories-emptied-regression.md`](spec/api-gaps/memories-emptied-regression.md). + The fix is **already written but unmerged**: BC3 #10947 (open, head + `9dc63e2e`) carries `json.memories @bubble_ups`. So this awaits #10947's + merge, not a decision. The live canary's `pairwiseSupersetArray: ["memories"]` + rule is waived via `pairwiseDeltaAllowed` until then. (There is no commit + `64acf34` — the once-assumed alias is counterfactual.) 2. Longstanding `app_todoslists_url` typo at - `app/views/api/todosets/_todoset.json.jbuilder:28` — server fix, alias - period, or SDK accommodation. The SDK spec models the correctly-spelled - `app_todolists_url`, so SDK clients reading that field have always seen - `null`. + `app/views/api/todosets/_todoset.json.jbuilder:28` — **the planned alias is + unshipped**: production `master` still carries only the typo. Impact is nil + for SDK consumers — the spec models the correctly-spelled `app_todolists_url`, + which has always read `null`, so there is no regression either way. -Both are tracked in the BC3 plan's Phase 2 compat verification and this -SDK plan's §8. +Both are tracked in the BC3 plan's Phase 2 compat verification and this SDK +plan's §8. ## Where to look diff --git a/spec/api-gaps/README.md b/spec/api-gaps/README.md index 773ee00b8..096ab0043 100644 --- a/spec/api-gaps/README.md +++ b/spec/api-gaps/README.md @@ -42,11 +42,18 @@ making the absorption journey publicly auditable. | [everything-aggregates](everything-aggregates.md) | no-json-contract | 3c | high | | [activity-timeline](activity-timeline.md) | no-json-contract | 3d | high | | [recordable-subtypes-doc](recordable-subtypes-doc.md) | partial-coverage | 3a | medium | -| [stack-doc-and-smithy](stack-doc-and-smithy.md) | partial-coverage | 3b | medium | +| [stack-doc-and-smithy](stack-doc-and-smithy.md) | confirmed-not-api-resource | 3b | medium | | [search-filter-additions](search-filter-additions.md) | no-json-contract | 3e | medium | | [rich-text-project-attachable](rich-text-project-attachable.md) | no-json-contract | 3e | low | | [recording-bubbleupable-field](recording-bubbleupable-field.md) | no-json-contract | 3e | low | | [todoset-completed-list-visibility](todoset-completed-list-visibility.md) | ambiguous | 3a | low | +| [memories-emptied-regression](memories-emptied-regression.md) | partial-coverage | launch | high | + +> `memories-emptied-regression` is a *subtractive regression* (a field BC4 +> populates that BC5 emptied), not an additive gap; `partial-coverage` is the +> closest status fit — see the entry. The fix is written but unmerged in BC3 +> #10947. `stack-doc-and-smithy` is retained as a `confirmed-not-api-resource` +> classification record (Stacks are web-only on both `four` and `master`). The detector also maintains [`allowlist.yml`](allowlist.yml) for routes classified as not-an-API-resource or absorbed under another entry. Allowlist diff --git a/spec/api-gaps/activity-timeline.md b/spec/api-gaps/activity-timeline.md index 125ff58a5..e1b09b998 100644 --- a/spec/api-gaps/activity-timeline.md +++ b/spec/api-gaps/activity-timeline.md @@ -27,6 +27,12 @@ Note: there is **no `/buckets/:id/timeline` route** — earlier drafts of this brief used the wrong path. Per-project routes are keyed by `project_id`, not `bucket_id`. +**Launch FYI (2026-05-27):** both `/activity.json` and +`/projects/:project_id/timeline.json` ship on `master` **directly** (not via +BC3 PR #10947), so SDK absorption pulls from `master`. The BC5-new +`/activity/days/:date` and `/activity/dates` sub-routes were removed in the +timeline rewrite — they were never modeled here, so there is nothing to remove. + ## Why it matters Activity feeds are a primary integration surface for dashboards, audit logs, diff --git a/spec/api-gaps/everything-aggregates.md b/spec/api-gaps/everything-aggregates.md index ba7ebc8ba..0fa11d8e5 100644 --- a/spec/api-gaps/everything-aggregates.md +++ b/spec/api-gaps/everything-aggregates.md @@ -7,22 +7,26 @@ bc3_refs: introduced_in: five bc3_plan_phase: 3c routes: - - "GET /:account_id/todos.json" - "GET /:account_id/todos/open.json" - - "GET /:account_id/cards.json" + - "GET /:account_id/todos/completed.json" + - "GET /:account_id/todos/overdue.json" + - "GET /:account_id/todos/unassigned.json" + - "GET /:account_id/todos/no_due_date.json" + - "GET /:account_id/cards/open.json" + - "GET /:account_id/cards/completed.json" + - "GET /:account_id/cards/overdue.json" + - "GET /:account_id/cards/unassigned.json" + - "GET /:account_id/cards/no_due_date.json" - "GET /:account_id/cards/not_now.json" - - "GET /:account_id/files.json" - - "GET /:account_id/messages.json" - - "GET /:account_id/comments.json" + - "GET /:account_id/documents/recent.json" + - "GET /:account_id/messages/recent.json" + - "GET /:account_id/comments/recent.json" + - "GET /:account_id/checkins/recent.json" + - "GET /:account_id/forwards/recent.json" - "GET /:account_id/boosts.json" - - "GET /:account_id/checkins.json" - - "GET /:account_id/forwards.json" - - "GET /:account_id/documents.json" - - "(plus per-group filter sub-routes; final route list pending BC3 settlement)" controllers: - app/controllers/everything/todos_controller.rb - app/controllers/everything/cards_controller.rb - - app/controllers/everything/files_controller.rb - app/controllers/everything/messages_controller.rb - app/controllers/everything/comments_controller.rb - app/controllers/everything/boosts_controller.rb @@ -40,15 +44,20 @@ bc3_refs: BC5 introduces account-wide flat listings of recordings by type, served by the `everything/*_controller.rb` namespace under flat top-level paths (note: `/everything/...` is the **Rails controller namespace**, not part of the URL). -There are 9 recording-type groups: todos, cards, files, messages, comments, -boosts, checkins, forwards, documents. +There are 8 active groups after the launch reconciliation: todos, cards, +messages, comments, boosts, checkins, forwards, documents (the files group was +dropped — see below). -**Current BC3 Phase 3c scope: 22 API-eligible endpoints.** The earlier top -summary listed 30 endpoints across the 9 groups (bare top-level routes plus -filter sub-routes per group); the Phase 3c detail narrowed it to 22 by -excluding the bare top-level routes that stay HTML shells. Treat 22 as the -working number for absorption planning; if BC3 settles a different final -count before the absorption PR opens, this brief gets re-synced. +**Current BC3 scope: 17 API-eligible endpoints** (BC3 PR #10947, open/unmerged). +The launch reconciliation (`BRIEF-bc5-reconciliation-scope-cuts.md`, `five+api` +@ `716e710ee5`) dropped the **files group**: `master` consolidated file-type +routes into `GET /files/recent.json?kind=` and unions unrenderable attachment +recordings (no API recordable-partial), so files are out of #10947's scope. Net +22 → 17. Bare collection routes (`/todos.json`, `/cards.json`, …) are HTML +shells and intentionally do **not** return JSON; the API surface is the named +subroutes in the frontmatter plus standalone `/boosts.json`. Files-by-kind +(`/files/recent?kind=`) is a possible *future* BC3 deliverable — file a fresh +brief then; do not model it speculatively now. ## Why it matters @@ -72,20 +81,22 @@ e.g. `/cards/not_now.json` is the per-status filter variant of `/cards.json`. ## Implementation notes for BC3 -- 9 controllers under `app/controllers/everything/` already exist (web). - Add a `respond_to :json` branch and corresponding jbuilder views. -- Consider whether to ship bare top-level routes (`/todos.json`, etc.) or - scope to sub-routes only. Mirror that decision in `doc/api/`. +- 8 controllers under `app/controllers/everything/` already exist (web; the + files group is out of scope — see What's missing). Add a `respond_to :json` + branch and corresponding jbuilder views. +- Bare top-level routes (`/todos.json`, etc.) stay HTML shells; the JSON + surface is the named subroutes only. Mirror that in `doc/api/`. - Consistency: all 9 groups should use the same pagination + filter idiom. Inconsistency between groups creates per-endpoint absorption work in the SDK. ## SDK absorption plan when this lands -- New `EverythingService` with one op per endpoint BC3 ships. +- New `EverythingService` with **17** operations (one per endpoint in the + frontmatter route list), opening once BC3 PR #10947 merges. - Each op routed at the flat top-level path (no `/everything/` URL prefix). - Reuse existing recording shapes (`Todo`, `Card`, `Document`, `Message`, etc.). - Canary fixture: cover at least one operation per group to catch shape drift. Pairwise check: BC4 absent → BC5 present is fine. -- Operation count in PR description must match what BC3 actually ships. - Working number is 22 (Phase 3c API-eligible endpoints); brief author - re-syncs route list before the absorption PR opens. +- Operation count in the PR description must match what #10947 actually ships + (17 after the files-group drop); re-sync the route list if BC3 settles a + different final count before the absorption PR opens. diff --git a/spec/api-gaps/memories-emptied-regression.md b/spec/api-gaps/memories-emptied-regression.md new file mode 100644 index 000000000..497d27b34 --- /dev/null +++ b/spec/api-gaps/memories-emptied-regression.md @@ -0,0 +1,78 @@ +--- +gap: memories-emptied-regression +status: partial-coverage +detected: 2026-05-27 +sdk_demand: high +bc3_pr: 10947 +bc3_refs: + introduced_in: master + routes: + - "GET /:account_id/my/readings.json" + related_existing_api: + - GetMyNotifications +--- + +# Memories emptied on BC5 (subtractive regression) + +> **Not an additive gap.** Every other entry in this registry tracks *new* BC5 +> surface awaiting JSON coverage. This entry tracks a *subtractive regression*: +> a field BC4 populates that BC5 emptied. `status: partial-coverage` is the +> closest schema fit — read it as "coverage regressed," not "coverage partially +> added." Status flips to `addressed-in-bc3-pr-10947` when #10947 merges. + +## What's missing + +`GET /:account_id/my/readings.json` emits a top-level `memories` array. On BC4 +(the `four` branch) it is populated; on BC5 (`master`, production) it ships as +an unconditional empty array. Source diff of +`app/views/api/my/readings/index.json.jbuilder`: + +- BC4 (`four`): `json.memories @memories, partial: "my/readings/reading", as: :reading` + — **populated**. +- BC5 (`master`, production): `json.memories []` — **unconditional empty + array**, no account-gating. + +The per-reading `memory_url` field is preserved on the wire; only the top-level +collection regressed. There is **no** "alias to `bubble_ups`" on production: +the once-assumed commit `64acf34` does not exist on `four`, `five`, or `master`. + +## Why it matters + +This is silent data loss for existing BC4 integrations: the request still +succeeds, the field is still present and still type-conformant (an array), so +per-backend schema validation passes on both sides. Only a *pairwise* BC4↔BC5 +comparison catches it — exactly the additive-only invariant the live canary +exists to enforce. It is the canonical demonstration of why per-backend schema +checks are necessary but not sufficient. + +## Suggested API shape + +No new shape is needed, and the SDK proposes none: BC3 has already chosen the +fix. Open PR **#10947** (head `9dc63e2e`) changes the jbuilder to +`json.memories @bubble_ups`, repopulating the top-level collection from the +Bubble Up successor so BC4 readers keep receiving a populated array. This entry +records that as the fix-in-flight rather than re-proposing a shape. + +## Implementation notes for BC3 + +- The fix is one line and **already written** in open PR #10947 + (`json.memories @bubble_ups`). This is a merge/ship item, not a design item. +- Until #10947 merges, production `master` stays regressed. +- BC3 provenance at the time of this finding: `five+api` @ `716e710ee5` + (the reconciliation handoff, suite green). + +## SDK absorption plan when this lands + +- **Now (regression live):** the canary's `pairwiseSupersetArray: ["memories"]` + rule on `GetMyNotifications` (`conformance/tests/live-my-surface.json`) + encodes the intended invariant but is **waived** by a temporary + `pairwiseDeltaAllowed: ["memories"]` entry pointing at this brief. The canary + stays green on the known/reported regression while still protecting every + other path. (That rule reframe + waiver lands in PR #308.) +- **On #10947 merge:** remove the `pairwiseDeltaAllowed` waiver (restoring the + hard-fail), repin `spec/api-provenance.json` past the fix, and flip this + entry's `status` to `addressed-in-bc3-pr-10947` (the `bc3_pr: 10947` field is + already set). +- No Smithy change is required — `memories` is already modeled on + `GetMyNotificationsOutput`, and its doc comment already records the observed + `[]` behavior. diff --git a/spec/api-gaps/stack-doc-and-smithy.md b/spec/api-gaps/stack-doc-and-smithy.md index 20d5e9676..c6eb6954a 100644 --- a/spec/api-gaps/stack-doc-and-smithy.md +++ b/spec/api-gaps/stack-doc-and-smithy.md @@ -1,75 +1,68 @@ --- gap: stack-doc-and-smithy -status: partial-coverage +status: confirmed-not-api-resource detected: 2026-05-01 sdk_demand: medium bc3_refs: introduced_in: five bc3_plan_phase: 3b + bc3_plan_section: "BC3 reconciliation handoff §1 (BRIEF-bc5-reconciliation-scope-cuts.md, five+api @ 716e710ee5); withdrawn from API scope at launch" routes: - - GET /:account_id/stacks/:id.json - - POST /:account_id/stacks.json - - PUT /:account_id/stacks/:id.json - - DELETE /:account_id/stacks/:id.json - - GET /:account_id/stacks/:id/collectables.json + - "(none — Stacks render web-only on four and master; no respond_to :json, no doc/api section)" controllers: - app/controllers/stacks_controller.rb - - app/controllers/stacks/collectables_controller.rb - related_existing_api: - - "(stack jbuilder partial — already shipped; doc + Smithy still missing)" + related_existing_api: [] --- -# Stack — full CRUD + list collectables +# Stack — withdrawn from API scope at launch (web-only) -## What's missing +> **Classification: not an API resource.** Retained as the durable record of +> why no `StacksService` is modeled. Registry rule: briefs trump the allowlist, +> so this stays a brief rather than moving to `allowlist.yml`. -Stacks (BC5's saved-collection abstraction) ship with a partial in -`app/views/api/stacks/_stack.json.jbuilder` already, but no documented JSON -contract or Smithy operations. The BC3 plan Phase 3b expanded this to **full -CRUD + list collectables** (revised from earlier "doc-only / add-remove -collectables" scope). +## What's missing -## Why it matters +Nothing for the SDK to absorb. At the BC5 launch reconciliation, Stacks were +confirmed **web-only on both BC4 (`four`) and BC5 (`master`)**: -Stacks are user-curated collections of recordings — first-class data the SDK -should be able to read and manipulate. Without CRUD, integrations can't -build "save to my stack" workflows or surface stack contents in dashboards. +- `StacksController` renders with `layout false` and has **no `respond_to` / + `format.json` branch** — there is no bearer-token JSON contract. +- There is **no `doc/api/sections/stacks.md`**. +- `Stacks::CollectablesController` **does not exist on `master`**. +- The `app/views/api/stacks/_stack.json.jbuilder` partial renders only when + **nested inside other responses**, never as a standalone Stack endpoint. -## Suggested API shape +Earlier drafts of this brief assumed BC3 Phase 3b would ship full Stack CRUD + +list-collectables JSON. The reconciliation handoff +(`BRIEF-bc5-reconciliation-scope-cuts.md`, `five+api` @ `716e710ee5`) withdrew +that: there is no public JSON Stack contract on either branch. -`GET /:account_id/stacks/:id.json`: -- `id`, `title`, `position`, `created_at`, `updated_at` -- `creator` (Person) -- `collectables_count`, `collectables_url` - -`POST /:account_id/stacks.json`: -- Input: `title`, optional `position` -- Returns the created stack. +## Why it matters -`PUT /:account_id/stacks/:id.json`: -- Input: subset of mutable fields (`title`, `position`). -- Returns the updated stack or 204. +It matters as a *negative* result. The SDK explicitly does **not** model a +`StacksService`, and the canary does not expect Stack endpoints on either +backend. Recording that here prevents a future detector run or contributor from +re-filing Stacks as an additive gap. If Stacks ever gain a JSON contract, file +a fresh additive brief then. -`DELETE /:account_id/stacks/:id.json`: -- Returns 204. +## Suggested API shape -`GET /:account_id/stacks/:id/collectables.json`: -- Pagination: Link header. -- Response: array of polymorphic Recording-shaped items. +None. Stacks have no public JSON request/response shape on `four` or `master`. +The nested `_stack` partial's fields are an implementation detail of whatever +parent response embeds it, not a standalone contract, so there is no shape to +propose. ## Implementation notes for BC3 -- The stack partial already exists; show/index views reuse it. -- Add `create`/`update`/`destroy` actions with JSON branches. -- New `_collectable.json.jbuilder` (or reuse existing recording partial) - for the collectables endpoint. -- `doc/api/sections/stacks.md` covers all five routes. +No SDK-facing action. If BC3 later decides to expose Stacks over JSON, that is +net-new API work: `respond_to :json` branches, standalone jbuilder views, a +`doc/api/sections/stacks.md`, and (if collectables are exposed) a +`Stacks::CollectablesController`. None of that exists today on either branch. ## SDK absorption plan when this lands -- New `StacksService` with `get(id)`, `create(input)`, `update(id, input)`, - `delete(id)`, `listCollectables(id)`. -- New shapes: `Stack`, `CreateStackInput/Output`, `UpdateStackInput/Output`. -- `listCollectables` uses Recording polymorphic shape. -- Canary fixture: `getStack(id)` once a stable fixture exists; pairwise - check is BC4-absent → BC5-present. +No absorption. **No `StacksService`** — none of `Get` / `Create` / `Update` / +`Delete` / `ListCollectables` are modeled, because none exist on the BC3 wire. +This brief is the classification record; `status: confirmed-not-api-resource` +reflects the launch decision. Should a JSON contract appear later, supersede +this entry with a fresh additive brief. From 06e2d7a437c4d737f11564fa013339d9701140b8 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 27 May 2026 16:41:07 -0700 Subject: [PATCH 2/9] spec(api-gaps): resolve #325 review threads (group count + activity status) - everything-aggregates.md: the leftover "all 9 groups" consistency bullet now reads "all 8 groups", matching the files-group drop above. - activity-timeline.md: reword the launch FYI so it no longer reads as a shipped, consumable JSON contract (which conflicted with status no-json-contract). The routes were rewritten on master directly (not gated behind #10947) but still lack a documented/SDK-absorbable contract, so the entry stays no-json-contract; absorption pulls from master when that lands. --- spec/api-gaps/activity-timeline.md | 12 +++++++----- spec/api-gaps/everything-aggregates.md | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/api-gaps/activity-timeline.md b/spec/api-gaps/activity-timeline.md index e1b09b998..b8fb15b6a 100644 --- a/spec/api-gaps/activity-timeline.md +++ b/spec/api-gaps/activity-timeline.md @@ -27,11 +27,13 @@ Note: there is **no `/buckets/:id/timeline` route** — earlier drafts of this brief used the wrong path. Per-project routes are keyed by `project_id`, not `bucket_id`. -**Launch FYI (2026-05-27):** both `/activity.json` and -`/projects/:project_id/timeline.json` ship on `master` **directly** (not via -BC3 PR #10947), so SDK absorption pulls from `master`. The BC5-new -`/activity/days/:date` and `/activity/dates` sub-routes were removed in the -timeline rewrite — they were never modeled here, so there is nothing to remove. +**Launch FYI (2026-05-27):** the activity routes were rewritten on `master` +**directly** (not gated behind BC3 PR #10947). They still lack a documented +(`doc/api`) and SDK-absorbable JSON contract, so this entry stays +`no-json-contract`; when that contract lands, absorption pulls from `master` +rather than from #10947. The BC5-new `/activity/days/:date` and +`/activity/dates` sub-routes were removed in the timeline rewrite — they were +never modeled here, so there is nothing to remove. ## Why it matters diff --git a/spec/api-gaps/everything-aggregates.md b/spec/api-gaps/everything-aggregates.md index 0fa11d8e5..81c622597 100644 --- a/spec/api-gaps/everything-aggregates.md +++ b/spec/api-gaps/everything-aggregates.md @@ -86,7 +86,7 @@ e.g. `/cards/not_now.json` is the per-status filter variant of `/cards.json`. branch and corresponding jbuilder views. - Bare top-level routes (`/todos.json`, etc.) stay HTML shells; the JSON surface is the named subroutes only. Mirror that in `doc/api/`. -- Consistency: all 9 groups should use the same pagination + filter idiom. +- Consistency: all 8 groups should use the same pagination + filter idiom. Inconsistency between groups creates per-endpoint absorption work in the SDK. ## SDK absorption plan when this lands From 287fd85682ca8151fccf77914a5bda123f0f1a75 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 27 May 2026 17:02:09 -0700 Subject: [PATCH 3/9] spec: align memories doc comment with the reconciliation + regenerate The GetMyNotificationsOutput.memories Smithy doc still framed the BC5 `[]` as an unresolved keep-vs-break decision and told readers to "consume both", which contradicts the reconciliation (PR #325): the decision is made and the fix is written but unmerged in BC3 #10947. Reword to state the confirmed regression, the in-flight fix, and "prefer bubble_ups; don't rely on memories being populated on BC5 until #10947 ships". Points at the regression brief. Regenerated the artifacts that inherit the @documentation trait: openapi.json + openapi-stripped.json, the TS schema.d.ts JSDoc, and the Go client.gen.go comment. Ruby/TS metadata + Ruby types.rb carry only a generated-timestamp bump (their generators don't emit this field description); Python/Kotlin/Swift generated output is unchanged. Drift checks (smithy-check, go-check-drift, behavior-model-check) clean. Addresses the Copilot/cubic doc-consistency thread on PR #325. --- go/pkg/generated/client.gen.go | 16 ++++++++-------- openapi.json | 2 +- spec/basecamp.smithy | 16 ++++++++-------- typescript/src/generated/openapi-stripped.json | 2 +- typescript/src/generated/schema.d.ts | 16 ++++++++-------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index 52422dc81..ceb87b237 100644 --- a/go/pkg/generated/client.gen.go +++ b/go/pkg/generated/client.gen.go @@ -1026,14 +1026,14 @@ type GetMyNotificationsResponseContent struct { // `scheduled_bubble_ups` for the time-deferred subset. BubbleUps []Notification `json:"bubble_ups,omitempty"` - // Memories Legacy "save forever" collection. Observed BC5 behavior: emits `[]` - // while BC4 still populates with real items — the BC team has not yet - // resolved whether to keep BC4-shaped data on BC5 (back-compat) or to - // accept the empty-array break with a documented BC5 changelog entry. - // See COORDINATION.md for the open decision. The conceptual - // replacement is `bubble_ups` (with optional scheduling via - // `scheduled_bubble_ups`), though wire shapes are not interchangeable - // per-item, so cross-version readers should consume both. + // Memories Legacy "save forever" collection. On BC5 `master` this currently ships as + // `[]` while BC4 (the `four` branch) still populates it — a confirmed + // subtractive regression tracked in + // `spec/api-gaps/memories-emptied-regression.md`. The fix is written but + // unmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New + // integrations should prefer `bubble_ups` (the durable successor, with + // optional scheduling via `scheduled_bubble_ups`); until #10947 ships, do + // not rely on `memories` being populated on BC5. Memories []Notification `json:"memories,omitempty"` Reads []Notification `json:"reads,omitempty"` diff --git a/openapi.json b/openapi.json index 9dc62c7f4..c6c3c146f 100644 --- a/openapi.json +++ b/openapi.json @@ -23585,7 +23585,7 @@ "items": { "$ref": "#/components/schemas/Notification" }, - "description": "Legacy \"save forever\" collection. Observed BC5 behavior: emits `[]`\nwhile BC4 still populates with real items — the BC team has not yet\nresolved whether to keep BC4-shaped data on BC5 (back-compat) or to\naccept the empty-array break with a documented BC5 changelog entry.\nSee COORDINATION.md for the open decision. The conceptual\nreplacement is `bubble_ups` (with optional scheduling via\n`scheduled_bubble_ups`), though wire shapes are not interchangeable\nper-item, so cross-version readers should consume both." + "description": "Legacy \"save forever\" collection. On BC5 `master` this currently ships as\n`[]` while BC4 (the `four` branch) still populates it — a confirmed\nsubtractive regression tracked in\n`spec/api-gaps/memories-emptied-regression.md`. The fix is written but\nunmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New\nintegrations should prefer `bubble_ups` (the durable successor, with\noptional scheduling via `scheduled_bubble_ups`); until #10947 ships, do\nnot rely on `memories` being populated on BC5." }, "bubble_ups": { "type": "array", diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index 1d742ee0d..e883f2201 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -8188,14 +8188,14 @@ structure GetMyNotificationsOutput { unreads: NotificationList reads: NotificationList - /// Legacy "save forever" collection. Observed BC5 behavior: emits `[]` - /// while BC4 still populates with real items — the BC team has not yet - /// resolved whether to keep BC4-shaped data on BC5 (back-compat) or to - /// accept the empty-array break with a documented BC5 changelog entry. - /// See COORDINATION.md for the open decision. The conceptual - /// replacement is `bubble_ups` (with optional scheduling via - /// `scheduled_bubble_ups`), though wire shapes are not interchangeable - /// per-item, so cross-version readers should consume both. + /// Legacy "save forever" collection. On BC5 `master` this currently ships as + /// `[]` while BC4 (the `four` branch) still populates it — a confirmed + /// subtractive regression tracked in + /// `spec/api-gaps/memories-emptied-regression.md`. The fix is written but + /// unmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New + /// integrations should prefer `bubble_ups` (the durable successor, with + /// optional scheduling via `scheduled_bubble_ups`); until #10947 ships, do + /// not rely on `memories` being populated on BC5. memories: NotificationList /// Items the user has saved with Bubble Up (BC5 addition). Roughly the diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 321696a30..6160bb0dd 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -21261,7 +21261,7 @@ "items": { "$ref": "#/components/schemas/Notification" }, - "description": "Legacy \"save forever\" collection. Observed BC5 behavior: emits `[]`\nwhile BC4 still populates with real items — the BC team has not yet\nresolved whether to keep BC4-shaped data on BC5 (back-compat) or to\naccept the empty-array break with a documented BC5 changelog entry.\nSee COORDINATION.md for the open decision. The conceptual\nreplacement is `bubble_ups` (with optional scheduling via\n`scheduled_bubble_ups`), though wire shapes are not interchangeable\nper-item, so cross-version readers should consume both." + "description": "Legacy \"save forever\" collection. On BC5 `master` this currently ships as\n`[]` while BC4 (the `four` branch) still populates it — a confirmed\nsubtractive regression tracked in\n`spec/api-gaps/memories-emptied-regression.md`. The fix is written but\nunmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New\nintegrations should prefer `bubble_ups` (the durable successor, with\noptional scheduling via `scheduled_bubble_ups`); until #10947 ships, do\nnot rely on `memories` being populated on BC5." }, "bubble_ups": { "type": "array", diff --git a/typescript/src/generated/schema.d.ts b/typescript/src/generated/schema.d.ts index f124dd366..66a71417a 100644 --- a/typescript/src/generated/schema.d.ts +++ b/typescript/src/generated/schema.d.ts @@ -3300,14 +3300,14 @@ export interface components { unreads?: components["schemas"]["Notification"][]; reads?: components["schemas"]["Notification"][]; /** - * @description Legacy "save forever" collection. Observed BC5 behavior: emits `[]` - * while BC4 still populates with real items — the BC team has not yet - * resolved whether to keep BC4-shaped data on BC5 (back-compat) or to - * accept the empty-array break with a documented BC5 changelog entry. - * See COORDINATION.md for the open decision. The conceptual - * replacement is `bubble_ups` (with optional scheduling via - * `scheduled_bubble_ups`), though wire shapes are not interchangeable - * per-item, so cross-version readers should consume both. + * @description Legacy "save forever" collection. On BC5 `master` this currently ships as + * `[]` while BC4 (the `four` branch) still populates it — a confirmed + * subtractive regression tracked in + * `spec/api-gaps/memories-emptied-regression.md`. The fix is written but + * unmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New + * integrations should prefer `bubble_ups` (the durable successor, with + * optional scheduling via `scheduled_bubble_ups`); until #10947 ships, do + * not rely on `memories` being populated on BC5. */ memories?: components["schemas"]["Notification"][]; /** From c30a28965876a0ab1e67c89e6692c8cefe02c480 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 27 May 2026 17:10:13 -0700 Subject: [PATCH 4/9] spec(api-gaps): clarify the memories waiver lives in PR #308, not this branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The brief's SDK-absorption runbook read as if the pairwiseDeltaAllowed waiver and conformance/tests/live-my-surface.json were present here, but those canary files are on PR #308's branch (this branch is the registry record only). Reword to state #308 introduces the waiver and that it gets removed from live-my-surface.json once #308 and BC3 #10947 both land. Also reframe the "no Smithy change" bullet to "no structural change" — this PR does realign the memories doc comment (documentation, not a shape change). --- spec/api-gaps/memories-emptied-regression.md | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/spec/api-gaps/memories-emptied-regression.md b/spec/api-gaps/memories-emptied-regression.md index 497d27b34..f1d160e62 100644 --- a/spec/api-gaps/memories-emptied-regression.md +++ b/spec/api-gaps/memories-emptied-regression.md @@ -63,16 +63,20 @@ records that as the fix-in-flight rather than re-proposing a shape. ## SDK absorption plan when this lands -- **Now (regression live):** the canary's `pairwiseSupersetArray: ["memories"]` - rule on `GetMyNotifications` (`conformance/tests/live-my-surface.json`) - encodes the intended invariant but is **waived** by a temporary - `pairwiseDeltaAllowed: ["memories"]` entry pointing at this brief. The canary - stays green on the known/reported regression while still protecting every - other path. (That rule reframe + waiver lands in PR #308.) -- **On #10947 merge:** remove the `pairwiseDeltaAllowed` waiver (restoring the - hard-fail), repin `spec/api-provenance.json` past the fix, and flip this - entry's `status` to `addressed-in-bc3-pr-10947` (the `bc3_pr: 10947` field is - already set). -- No Smithy change is required — `memories` is already modeled on - `GetMyNotificationsOutput`, and its doc comment already records the observed - `[]` behavior. +- **Now (regression live):** the live-canary invariant for `memories` lives in + **PR #308**, not on this branch. That PR adds the + `pairwiseSupersetArray: ["memories"]` rule on `GetMyNotifications` plus a + temporary `pairwiseDeltaAllowed: ["memories"]` waiver (in + `conformance/tests/live-my-surface.json`, which exists on #308's branch) that + keeps the canary green on this known regression while still protecting every + other path. This branch carries the registry record only — no canary files — + and is what that waiver points back to. +- **Once PR #308 and BC3 #10947 have both landed:** remove the + `pairwiseDeltaAllowed: ["memories"]` waiver from + `conformance/tests/live-my-surface.json` (restoring the hard-fail), repin + `spec/api-provenance.json` past the fix, and flip this entry's `status` to + `addressed-in-bc3-pr-10947` (the `bc3_pr: 10947` field is already set). +- No **structural** Smithy change is required — `memories` is already modeled + on `GetMyNotificationsOutput`. (This PR does realign that field's doc comment + to describe the regression + the #10947 fix and regenerates the artifacts + that inherit it; that is documentation, not a shape change.) From f3fc0a830dedfb4688af31eac67bd98da434ce0f Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 27 May 2026 17:17:18 -0700 Subject: [PATCH 5/9] docs(coordination): attribute the memories canary waiver to PR #308 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit COORDINATION.md said the pairwiseSupersetArray rule "is waived via pairwiseDeltaAllowed", which reads as if the canary surface exists on this branch — it does not (conformance/tests/live-my-surface.json is on PR #308). Reword to attribute the rule + waiver to #308's branch, matching the same fix already made in the memories-emptied-regression brief. --- COORDINATION.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/COORDINATION.md b/COORDINATION.md index 055b681f5..a2dfe89c0 100644 --- a/COORDINATION.md +++ b/COORDINATION.md @@ -39,8 +39,11 @@ remain **open** — an earlier draft of this file framed them as pending a [`spec/api-gaps/memories-emptied-regression.md`](spec/api-gaps/memories-emptied-regression.md). The fix is **already written but unmerged**: BC3 #10947 (open, head `9dc63e2e`) carries `json.memories @bubble_ups`. So this awaits #10947's - merge, not a decision. The live canary's `pairwiseSupersetArray: ["memories"]` - rule is waived via `pairwiseDeltaAllowed` until then. (There is no commit + merge, not a decision. PR #308's live canary encodes the intended invariant + (`pairwiseSupersetArray: ["memories"]` on `GetMyNotifications`) and + temporarily waives it via `pairwiseDeltaAllowed` until #10947 merges; that + canary surface lives on #308's branch + (`conformance/tests/live-my-surface.json`), not here. (There is no commit `64acf34` — the once-assumed alias is counterfactual.) 2. Longstanding `app_todoslists_url` typo at `app/views/api/todosets/_todoset.json.jbuilder:28` — **the planned alias is From e663d8a0d0e3b593718cccdfd3a63746e969489b Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Mon, 13 Jul 2026 19:40:06 -0700 Subject: [PATCH 6/9] spec(api-gaps): refresh everything-aggregates from the live #10947 contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The brief captured the launch-reconciliation interim state: a 17-operation surface with the files group cut and the feed groups framed as recent-suffixed sub-routes. doc/api/sections/everything.md at #10947's live head 589b1970 (verified 2026-07-13) supersedes that: - 18 documented GET operations across 9 groups: todos x5 and cards x6 via filtered sub-routes, plus 7 root-path feeds (documents, messages, comments, checkins, forwards, files, boosts). - The files group is restored (GET /files.json with kind and people_ids[] filters). - Root collections are the documented contract for the feed groups; only the bare /todos.json and /cards.json roots remain HTML shells. - //recent.json paths are internal web Turbo-frame feeds, not API surface — the SDK must not model them. Also repin the #10947 head references in memories-emptied-regression.md and COORDINATION.md from 9dc63e2e to 589b1970. --- COORDINATION.md | 5 +- spec/api-gaps/everything-aggregates.md | 106 ++++++++++++------- spec/api-gaps/memories-emptied-regression.md | 8 +- 3 files changed, 75 insertions(+), 44 deletions(-) diff --git a/COORDINATION.md b/COORDINATION.md index a2dfe89c0..25da2a2a7 100644 --- a/COORDINATION.md +++ b/COORDINATION.md @@ -38,8 +38,9 @@ remain **open** — an earlier draft of this file framed them as pending a Filed as [`spec/api-gaps/memories-emptied-regression.md`](spec/api-gaps/memories-emptied-regression.md). The fix is **already written but unmerged**: BC3 #10947 (open, head - `9dc63e2e`) carries `json.memories @bubble_ups`. So this awaits #10947's - merge, not a decision. PR #308's live canary encodes the intended invariant + `589b1970`, verified 2026-07-13) carries `json.memories @bubble_ups`. So + this awaits #10947's merge, not a decision. PR #308's live canary encodes + the intended invariant (`pairwiseSupersetArray: ["memories"]` on `GetMyNotifications`) and temporarily waives it via `pairwiseDeltaAllowed` until #10947 merges; that canary surface lives on #308's branch diff --git a/spec/api-gaps/everything-aggregates.md b/spec/api-gaps/everything-aggregates.md index 81c622597..e771788f9 100644 --- a/spec/api-gaps/everything-aggregates.md +++ b/spec/api-gaps/everything-aggregates.md @@ -18,11 +18,12 @@ bc3_refs: - "GET /:account_id/cards/unassigned.json" - "GET /:account_id/cards/no_due_date.json" - "GET /:account_id/cards/not_now.json" - - "GET /:account_id/documents/recent.json" - - "GET /:account_id/messages/recent.json" - - "GET /:account_id/comments/recent.json" - - "GET /:account_id/checkins/recent.json" - - "GET /:account_id/forwards/recent.json" + - "GET /:account_id/documents.json" + - "GET /:account_id/messages.json" + - "GET /:account_id/comments.json" + - "GET /:account_id/checkins.json" + - "GET /:account_id/forwards.json" + - "GET /:account_id/files.json" - "GET /:account_id/boosts.json" controllers: - app/controllers/everything/todos_controller.rb @@ -33,6 +34,7 @@ bc3_refs: - app/controllers/everything/checkins_controller.rb - app/controllers/everything/forwards_controller.rb - app/controllers/everything/documents_controller.rb + - app/controllers/everything/files_controller.rb related_existing_api: - ListMyAssignments (similar contract — flat aggregate of one recording type) --- @@ -44,20 +46,32 @@ bc3_refs: BC5 introduces account-wide flat listings of recordings by type, served by the `everything/*_controller.rb` namespace under flat top-level paths (note: `/everything/...` is the **Rails controller namespace**, not part of the URL). -There are 8 active groups after the launch reconciliation: todos, cards, -messages, comments, boosts, checkins, forwards, documents (the files group was -dropped — see below). - -**Current BC3 scope: 17 API-eligible endpoints** (BC3 PR #10947, open/unmerged). -The launch reconciliation (`BRIEF-bc5-reconciliation-scope-cuts.md`, `five+api` -@ `716e710ee5`) dropped the **files group**: `master` consolidated file-type -routes into `GET /files/recent.json?kind=` and unions unrenderable attachment -recordings (no API recordable-partial), so files are out of #10947's scope. Net -22 → 17. Bare collection routes (`/todos.json`, `/cards.json`, …) are HTML -shells and intentionally do **not** return JSON; the API surface is the named -subroutes in the frontmatter plus standalone `/boosts.json`. Files-by-kind -(`/files/recent?kind=`) is a possible *future* BC3 deliverable — file a fresh -brief then; do not model it speculatively now. +There are 9 groups: todos, cards, messages, comments, boosts, checkins, +forwards, documents, and files. + +**Current BC3 scope: 18 documented GET operations across 9 groups**, per +`doc/api/sections/everything.md` in BC3 PR #10947 (open/unmerged, head +`589b1970`, verified 2026-07-13): todos ×5 and cards ×6 via filtered +sub-routes (`open`, `completed`, `overdue`, `unassigned`, `no_due_date`, plus +`not_now` for cards), and 7 root-path feeds — `/documents.json`, +`/messages.json`, `/comments.json`, `/checkins.json`, `/forwards.json`, +`/files.json`, `/boosts.json`. + +Superseded interim history: the launch reconciliation +(`BRIEF-bc5-reconciliation-scope-cuts.md`, `five+api` @ `716e710ee5`) briefly +scoped this to a 17-operation surface with the files group excluded, and +framed the feed groups as `recent`-suffixed sub-routes. #10947 has since +**restored the files group** (`GET /files.json`, with `kind` and +`people_ids[]` filters) +and documented the feed groups at their **root paths**. Only the bare +`/todos.json` and `/cards.json` roots remain HTML shells — use the filtered +sub-routes for those two groups. + +The `//recent.json` paths still exist in the web app as internal +Turbo-frame feeds, but they are **not** API surface. Per +`doc/api/sections/everything.md`: "those are internal: the root collection is +the documented API contract. Don't depend on the `/recent` paths." The SDK +must not model them. ## Why it matters @@ -69,34 +83,50 @@ and racy with project-membership changes. ## Suggested API shape -Each endpoint mirrors the `ListMyAssignments`-shaped contract: a paginated -list of recordings of a single type, scoped to the current user's visibility. +`doc/api/sections/everything.md` (#10947, head `589b1970`) documents two +contract families: + +- **Bucket-grouped lists** — the todos/cards filter sub-routes (`/todos/open.json`, + `/cards/not_now.json`, …) return a paginated array of buckets, each grouping + the matching recordings (and their steps) under their parent project. +- **Flat recording lists** — `/todos/overdue.json`, `/cards/overdue.json`, and + the root feeds (`/documents.json`, `/messages.json`, `/comments.json`, + `/checkins.json`, `/forwards.json`, `/files.json`, `/boosts.json`) return a + flat array of recording objects, each embedding its `bucket` for project + context. Feeds are newest-first and paginated; overdue lists sort + oldest-first by due date. -Example: `GET /:account_id/todos/open.json`: -- Pagination: Link header (RFC5988), `X-Total-Count`, `maxPageSize: 50`. -- Response: array of Todo (per existing `_todo.json.jbuilder`). +`GET /files.json` additionally takes `kind` (`all` | `images` | `pdfs` | +`documents` | `videos`) and repeatable `people_ids[]` query filters, and mixes +uploads with rich-text attachments (attachments wrapped in a recording +envelope plus `attachable_sgid` and blob metadata). -Filter sub-routes follow the existing convention used by `MyAssignments` — -e.g. `/cards/not_now.json` is the per-status filter variant of `/cards.json`. +Root feeds are documented at their root paths; todos and cards are reachable +only via the filtered sub-routes (their bare roots stay HTML shells). ## Implementation notes for BC3 -- 8 controllers under `app/controllers/everything/` already exist (web; the - files group is out of scope — see What's missing). Add a `respond_to :json` - branch and corresponding jbuilder views. -- Bare top-level routes (`/todos.json`, etc.) stay HTML shells; the JSON - surface is the named subroutes only. Mirror that in `doc/api/`. -- Consistency: all 8 groups should use the same pagination + filter idiom. +- 9 controllers under `app/controllers/everything/` (the files group is back + in scope — see What's missing). +- Feed groups serve JSON at their root paths; the bare `/todos.json` and + `/cards.json` roots stay HTML shells with the filtered sub-routes as the + JSON surface. `//recent.json` stays an internal web Turbo-frame + feed — keep it out of `doc/api/`. +- Consistency: all 9 groups should use the same pagination + filter idiom. Inconsistency between groups creates per-endpoint absorption work in the SDK. ## SDK absorption plan when this lands -- New `EverythingService` with **17** operations (one per endpoint in the - frontmatter route list), opening once BC3 PR #10947 merges. +- New `EverythingService` with **18** operations matching the documented + contract (one per endpoint in the frontmatter route list), opening once BC3 + PR #10947 merges. Re-derive the operation list from + `doc/api/sections/everything.md` at the merge head at absorption time. +- Do **not** model the `//recent.json` aliases unless explicitly + approved — they are internal web feeds, not API contract. - Each op routed at the flat top-level path (no `/everything/` URL prefix). - Reuse existing recording shapes (`Todo`, `Card`, `Document`, `Message`, etc.). -- Canary fixture: cover at least one operation per group to catch shape drift. - Pairwise check: BC4 absent → BC5 present is fine. +- Canary fixture: cover at least one operation per group (9 groups) to catch + shape drift. Pairwise check: BC4 absent → BC5 present is fine. - Operation count in the PR description must match what #10947 actually ships - (17 after the files-group drop); re-sync the route list if BC3 settles a - different final count before the absorption PR opens. + (18 as of head `589b1970`, 2026-07-13); re-sync the route list if BC3 + settles a different final count before the absorption PR opens. diff --git a/spec/api-gaps/memories-emptied-regression.md b/spec/api-gaps/memories-emptied-regression.md index f1d160e62..7b210c371 100644 --- a/spec/api-gaps/memories-emptied-regression.md +++ b/spec/api-gaps/memories-emptied-regression.md @@ -48,10 +48,10 @@ checks are necessary but not sufficient. ## Suggested API shape No new shape is needed, and the SDK proposes none: BC3 has already chosen the -fix. Open PR **#10947** (head `9dc63e2e`) changes the jbuilder to -`json.memories @bubble_ups`, repopulating the top-level collection from the -Bubble Up successor so BC4 readers keep receiving a populated array. This entry -records that as the fix-in-flight rather than re-proposing a shape. +fix. Open PR **#10947** (head `589b1970`, verified 2026-07-13) changes the +jbuilder to `json.memories @bubble_ups`, repopulating the top-level collection +from the Bubble Up successor so BC4 readers keep receiving a populated array. +This entry records that as the fix-in-flight rather than re-proposing a shape. ## Implementation notes for BC3 From b36ca17ce7d5a38b36a8e41a09e890698912af4c Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 21 Jul 2026 19:57:21 -0700 Subject: [PATCH 7/9] spec(api-gaps): reconcile registry with the shipped BC5 API train MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BC3's BC5 API train merged to master 2026-07-18..21 (8 PRs: #11628 BC4 wire-format compatibility, #12320 cloud files + google documents, #12321 calendars, #12322 My Notes, #12323 docs true-up, #11627 Everything feeds, #11629 doc-generation tooling, #12357 cURL examples). BC3 #10947 closed unmerged, superseded by the train — every "when #10947 merges" trigger is dead. Refresh the registry to how the train actually shipped: - memories-emptied-regression -> addressed-in-bc3-pr-11628: permanently empty by documented contract (my_notifications.md codifies the always-empty placeholder; the @bubble_ups alias never shipped). The #308 canary waiver is now permanent. New bubble-up surface (bubble_ups_count, scheduled_bubble_ups_count, limit_bubble_ups, GET /my/readings/bubble_ups.json) noted as future absorption items. - everything-aggregates -> addressed-in-bc3-pr-11627: re-derived to the exact 17 documented ops (todos x5, cards x6, six flat roots); no /documents.json root; bare /todos.json + /cards.json are HTML shells; /recent.json stays internal-never-model; bucket-grouped vs flat contract families documented. - activity-timeline -> addressed-in-bc3-pr-11629: re-derived to the 3-route surface (account /reports/progress.json, project timeline, person /reports/users/progress/:id.json object wrapper) — a BC4-era contract BC5 kept, re-verified against live BC5 by the #11629 doc tooling; SDK already models all 3 routes, remainder is additive fields (15-value kind vocabulary, schedule-entry data struct, avatars_sample, attachments). - calendar -> addressed-in-bc3-pr-12321; scratchpad -> addressed-in-bc3-pr-12322 (shipped route is /my/notes.json). - step-top-level -> absorbed-in-sdk: all five top-level ops already modeled in spec/basecamp.smithy with due_on + assignee_ids on create/update, matching card_table_steps.md (legacy assignees comma-string deliberately unmodeled). - recordable-subtypes-doc stays partial-coverage: CloudFile + GoogleDocument shipped via #12320; Journal dropped from the train (#11629 "Drop journal doc generation"). - search-filter-additions stays partial-coverage: docs shipped but open #12361 reworks the filter params — not final, don't absorb yet. - todoset-completed-list-visibility stays ambiguous; open #12021 cited as the deciding PR. - stack-doc-and-smithy stays confirmed-not-api-resource: product renamed Stacks -> Folders, API being scoped server-side, wire type still Stack. - rich-text-project-attachable + recording-bubbleupable-field stay no-json-contract (spot-checked merged docs: no Project-attachable contract in rich_text.md, no bubbleupable anywhere in doc/api). - README + COORDINATION: statuses updated; memories settled as permanent-by-contract; app_todoslists_url typo re-verified 2026-07-21 (still only the typo, no alias, no SDK action). --- COORDINATION.md | 61 ++++---- spec/api-gaps/README.md | 30 ++-- spec/api-gaps/activity-timeline.md | 111 +++++++------ spec/api-gaps/calendar.md | 56 +++---- spec/api-gaps/everything-aggregates.md | 147 +++++++++--------- spec/api-gaps/memories-emptied-regression.md | 124 +++++++++------ spec/api-gaps/recordable-subtypes-doc.md | 118 ++++++-------- spec/api-gaps/recording-bubbleupable-field.md | 5 + spec/api-gaps/rich-text-project-attachable.md | 11 +- spec/api-gaps/scratchpad.md | 68 ++++---- spec/api-gaps/search-filter-additions.md | 34 ++-- spec/api-gaps/stack-doc-and-smithy.md | 22 ++- spec/api-gaps/step-top-level.md | 73 +++++---- .../todoset-completed-list-visibility.md | 13 +- 14 files changed, 470 insertions(+), 403 deletions(-) diff --git a/COORDINATION.md b/COORDINATION.md index 25da2a2a7..7e46abad2 100644 --- a/COORDINATION.md +++ b/COORDINATION.md @@ -1,8 +1,10 @@ # Coordination: SDK ↔ BC3 API Parity for BC5 -This SDK works in lockstep with the BC3 API parity plan, tracked on the -`basecamp/bc3` `five+api` branch. The authoritative server-side audit lives -in that branch under `tmp/api_audit.md` (generated by the BC3 plan's +This SDK works in lockstep with the BC3 API parity plan. With the BC5 API +train merged (8 PRs to `basecamp/bc3` `master`, 2026-07-18..21), the contract +authority is **`doc/api/` on `master`** — doc examples are regenerated from +live BC5 by the #11629 tooling. The historical server-side audit lived on the +`five+api` branch under `tmp/api_audit.md` (generated by the BC3 plan's `script/api/audit`). ## Division of labor @@ -29,36 +31,41 @@ in that branch under `tmp/api_audit.md` (generated by the BC3 plan's ## Contract decisions (cross-team) -Two items the SDK canary surfaced. As of the BC5 launch (2026-05-27) both -remain **open** — an earlier draft of this file framed them as pending a -*decision* when they are actually pending a *merge*: +Two items the SDK canary surfaced. As of the BC5 API train (8 BC3 PRs merged +to `master`, 2026-07-18..21) both are **settled**: -1. `memories` going to `[]` on `GET /my/readings.json` — **confirmed live on - BC5 `master`** (source-diffed against `four`, which still populates it). - Filed as - [`spec/api-gaps/memories-emptied-regression.md`](spec/api-gaps/memories-emptied-regression.md). - The fix is **already written but unmerged**: BC3 #10947 (open, head - `589b1970`, verified 2026-07-13) carries `json.memories @bubble_ups`. So - this awaits #10947's merge, not a decision. PR #308's live canary encodes - the intended invariant - (`pairwiseSupersetArray: ["memories"]` on `GetMyNotifications`) and - temporarily waives it via `pairwiseDeltaAllowed` until #10947 merges; that - canary surface lives on #308's branch - (`conformance/tests/live-my-surface.json`), not here. (There is no commit - `64acf34` — the once-assumed alias is counterfactual.) +1. `memories` going to `[]` on `GET /my/readings.json` — **settled: + permanently empty by documented contract**. + `doc/api/sections/my_notifications.md` (language codified by BC3 #11628) + documents `memories` as an always-empty placeholder replaced by + `bubble_ups`. BC3 #10947, which would have repopulated it via + `json.memories @bubble_ups`, **closed unmerged** — superseded by the train; + the alias never shipped and never will. Filed as + [`spec/api-gaps/memories-emptied-regression.md`](spec/api-gaps/memories-emptied-regression.md) + (status `addressed-in-bc3-pr-11628`). PR #308's live canary encodes the + additive-only invariant + (`pairwiseSupersetArray: ["memories"]` on `GetMyNotifications`) with a + `pairwiseDeltaAllowed: ["memories"]` waiver that is now **permanent** — + the machine-readable record of the accepted BC4→BC5 delta; that canary + surface lives on #308's branch (`conformance/tests/live-my-surface.json`), + not here. Retire the waiver only if BC4 empties `memories` or BC5's + documented contract changes. (There is no commit `64acf34` — the + once-assumed alias is counterfactual.) 2. Longstanding `app_todoslists_url` typo at - `app/views/api/todosets/_todoset.json.jbuilder:28` — **the planned alias is - unshipped**: production `master` still carries only the typo. Impact is nil - for SDK consumers — the spec models the correctly-spelled `app_todolists_url`, - which has always read `null`, so there is no regression either way. + `app/views/api/todosets/_todoset.json.jbuilder:28` — unchanged by the + train: post-train `master` still carries **only the typo**, no + correctly-spelled alias (re-verified 2026-07-21). Impact is nil for SDK + consumers — the spec models the correctly-spelled `app_todolists_url`, + which has always read `null`, so there is no regression either way. No SDK + action needed. -Both are tracked in the BC3 plan's Phase 2 compat verification and this SDK -plan's §8. +Both records live on in the registry and this SDK plan's §8. ## Where to look -- BC3-side authoritative inventory: `tmp/api_audit.md` (generated by BC3 - plan's `script/api/audit`) +- BC3-side contract authority: `doc/api/` on `basecamp/bc3` `master` + (post-train; the historical `tmp/api_audit.md` inventory lived on + `five+api`) - SDK-side absorption status: [`spec/api-gaps/README.md`](spec/api-gaps/README.md) - Live canary results: `make check-bc5-compat` against current refs (lands in PR 4 of the SDK plan) diff --git a/spec/api-gaps/README.md b/spec/api-gaps/README.md index 096ab0043..ad8ff9dab 100644 --- a/spec/api-gaps/README.md +++ b/spec/api-gaps/README.md @@ -36,24 +36,28 @@ making the absorption journey publicly auditable. | Gap | Status | BC3 plan phase | SDK demand | |---|---|---|---| -| [calendar](calendar.md) | no-json-contract | 3b | medium | -| [scratchpad](scratchpad.md) | no-json-contract | 3b | medium | -| [step-top-level](step-top-level.md) | partial-coverage | 3b | low | -| [everything-aggregates](everything-aggregates.md) | no-json-contract | 3c | high | -| [activity-timeline](activity-timeline.md) | no-json-contract | 3d | high | +| [calendar](calendar.md) | addressed-in-bc3-pr-12321 | 3b | medium | +| [scratchpad](scratchpad.md) | addressed-in-bc3-pr-12322 | 3b | medium | +| [step-top-level](step-top-level.md) | absorbed-in-sdk | 3b | low | +| [everything-aggregates](everything-aggregates.md) | addressed-in-bc3-pr-11627 | 3c | high | +| [activity-timeline](activity-timeline.md) | addressed-in-bc3-pr-11629 | 3d | high | | [recordable-subtypes-doc](recordable-subtypes-doc.md) | partial-coverage | 3a | medium | | [stack-doc-and-smithy](stack-doc-and-smithy.md) | confirmed-not-api-resource | 3b | medium | -| [search-filter-additions](search-filter-additions.md) | no-json-contract | 3e | medium | +| [search-filter-additions](search-filter-additions.md) | partial-coverage | 3e | medium | | [rich-text-project-attachable](rich-text-project-attachable.md) | no-json-contract | 3e | low | | [recording-bubbleupable-field](recording-bubbleupable-field.md) | no-json-contract | 3e | low | | [todoset-completed-list-visibility](todoset-completed-list-visibility.md) | ambiguous | 3a | low | -| [memories-emptied-regression](memories-emptied-regression.md) | partial-coverage | launch | high | - -> `memories-emptied-regression` is a *subtractive regression* (a field BC4 -> populates that BC5 emptied), not an additive gap; `partial-coverage` is the -> closest status fit — see the entry. The fix is written but unmerged in BC3 -> #10947. `stack-doc-and-smithy` is retained as a `confirmed-not-api-resource` -> classification record (Stacks are web-only on both `four` and `master`). +| [memories-emptied-regression](memories-emptied-regression.md) | addressed-in-bc3-pr-11628 | launch | high | + +> Statuses reflect how BC3's **BC5 API train** actually shipped (8 PRs merged +> to `master`, 2026-07-18..21); BC3 #10947 closed unmerged, superseded by the +> train. `memories-emptied-regression` is a *subtractive* delta (a field BC4 +> populates that BC5 emptied), settled as **permanently empty by documented +> contract** — its `addressed-in-bc3-pr-11628` records the PR that codified +> the empty-placeholder contract, not a repopulation; see the entry. +> `stack-doc-and-smithy` is retained as a `confirmed-not-api-resource` +> classification record (Stacks — renamed Folders in the product — are +> web-only on both `four` and `master`). The detector also maintains [`allowlist.yml`](allowlist.yml) for routes classified as not-an-API-resource or absorbed under another entry. Allowlist diff --git a/spec/api-gaps/activity-timeline.md b/spec/api-gaps/activity-timeline.md index b8fb15b6a..fb526f2d2 100644 --- a/spec/api-gaps/activity-timeline.md +++ b/spec/api-gaps/activity-timeline.md @@ -1,78 +1,97 @@ --- gap: activity-timeline -status: no-json-contract +status: addressed-in-bc3-pr-11629 detected: 2026-05-01 sdk_demand: high +bc3_pr: 11629 bc3_refs: introduced_in: five bc3_plan_phase: 3d routes: - - GET /:account_id/activity.json + - GET /:account_id/reports/progress.json - GET /:account_id/projects/:project_id/timeline.json + - GET /:account_id/reports/users/progress/:person_id.json controllers: - - app/controllers/activity_controller.rb - app/controllers/timelines_controller.rb - related_existing_api: [] + - app/controllers/users/timelines_controller.rb + related_existing_api: + - GetProgressReport + - GetProjectTimeline + - GetPersonProgress --- -# Activity Timeline (global + per-project) +# Activity Timeline (account, project, and person) ## What's missing -BC5 introduces a global activity feed (`/activity.json`) and per-project -timeline (`/projects/:project_id/timeline.json`) backed by an Activity -envelope. The BC3 plan Phase 3d ships both routes plus the envelope shape. +Additive fields only — the routes and base contract are documented and already +modeled. The merged `doc/api/sections/timeline.md` on `master` documents +exactly three routes: -Note: there is **no `/buckets/:id/timeline` route** — earlier drafts of this -brief used the wrong path. Per-project routes are keyed by `project_id`, not -`bucket_id`. +- **Account** — `GET /reports/progress.json`: a paginated bare array of + timeline events across all projects the authenticated user can access. +- **Project** — `GET /projects/:project_id/timeline.json`: the same event + shape, pre-filtered to one project, also a bare array. +- **Person** — `GET /reports/users/progress/:person_id.json`: a JSON + **object** `{person, events}` — the person plus a paginated `events` list of + timeline events they created. -**Launch FYI (2026-05-27):** the activity routes were rewritten on `master` -**directly** (not gated behind BC3 PR #10947). They still lack a documented -(`doc/api`) and SDK-absorbable JSON contract, so this entry stays -`no-json-contract`; when that contract lands, absorption pulls from `master` -rather than from #10947. The BC5-new `/activity/days/:date` and -`/activity/dates` sub-routes were removed in the timeline rewrite — they were -never modeled here, so there is nothing to remove. +These routes **predate the BC5 API train**: they are a BC4-era contract, +documented since the #9981 docs repatriation, that BC5 kept. The train PR that +re-verified and regenerated `timeline.md` against live BC5 is BC3 **#11629** +(the doc-generation tooling PR), which is what `addressed-in-bc3-pr-11629` +records here. + +Historical corrections retained from earlier drafts: there is no +`/activity.json` route and no `/buckets/:id/timeline` route; the BC5-new +`/activity/days/:date` and `/activity/dates` sub-routes were removed in the +timeline rewrite and were never modeled here. ## Why it matters Activity feeds are a primary integration surface for dashboards, audit logs, -and "what's new since I last checked" tooling. Without a JSON API, consumers -have to poll individual recording endpoints and reconstruct ordering — slow, -incomplete, and incompatible with the in-app activity stream. +and "what's new since I last checked" tooling. The SDK already models all +three routes (`GetProgressReport`, `GetProjectTimeline`, `GetPersonProgress` +at `spec/basecamp.smithy:7084`, `:7105`, `:7130`, including the person-route +`{person, events}` object wrapper), so consumers can call them today — but the +event payload's typed surface lags the merged doc, leaving fields to be +consumed untyped. ## Suggested API shape -`GET /:account_id/activity.json`: -- Pagination: Link header, `X-Total-Count`. -- Response: array of `Activity` envelope objects: - - `id` (long), `created_at`, `updated_at` - - `kind` (string — what happened) - - `recording` (polymorphic: Todo, Card, Message, etc.) - - `creator` (Person) - - `bucket` (TodoBucket-shaped, when scoped) +The remaining absorption is additive fields on the event shape, per the merged +`doc/api/sections/timeline.md`: -`GET /:account_id/projects/:project_id/timeline.json`: -- Same envelope, pre-filtered to the project. -- Pagination: same as global. +- `kind` — a 15-value vocabulary: `message_created`, `comment_created`, + `todo_created`, `todo_completed`, `upload_created`, `document_created`, + `schedule_entry_created`, `schedule_entry_rescheduled`, `question_created`, + `question_answer_created`, `chat_transcript_rollup`, `kanban_card_created`, + `kanban_card_completed`, `inbox_forward_created`, + `client_correspondence_created`. +- `data` — event-specific payload; for `schedule_entry_created` / + `schedule_entry_rescheduled` it carries `{all_day, starts_at, ends_at}`. +- `avatars_sample` — array of avatar URLs (used by chat rollups to show + participants). +- `attachments` — array of attached files, if any. +- Plus the documented envelope fields (`parent_recording_id`, `action`, + `target`, `title`, `summary_excerpt`, `bucket`, `creator`, `url`, + `app_url`). ## Implementation notes for BC3 -- Activity envelope likely already exists in some form (used by web UI). BC3 - Phase 3d work makes it a documented, JSON-serialisable contract. -- Per-project timeline action lives on `timelines_controller.rb`, not on - `projects_controller`. -- `doc/api/sections/activity.md` covers both the global and per-project - routes plus the envelope shape. +Shipped — nothing pending. The account and project routes serve from +`timelines_controller.rb` and the person route from +`users/timelines_controller.rb`; `doc/api/sections/timeline.md` is regenerated +against live BC5 by the doc tooling from #11629. ## SDK absorption plan when this lands -- New `ActivityService` with `getActivity()` (global) and - `getProjectTimeline(projectId)` (per-project). -- New shape: `Activity` envelope. -- The recording field is polymorphic — model as a union of existing recording - shapes if Smithy supports it, otherwise use a discriminated structure with - a `type` field. -- Canary fixture: include `getActivity()` once schema is stable. Pairwise - check is BC4-absent → BC5-present. +- No new operations — `GetProgressReport`, `GetProjectTimeline`, and + `GetPersonProgress` already exist with the correct paths and the + person-route object wrapper. +- Extend the timeline event shape with the additive fields above: the `kind` + vocabulary (model as an open string enum — BC3 says "common values + include", so treat it as non-exhaustive), the `data` struct for + schedule-entry events, `avatars_sample`, and `attachments`. +- Canary fixture: `GetProgressReport` exercises the account feed; pairwise + check is structural (the routes exist on both BC4 and BC5). diff --git a/spec/api-gaps/calendar.md b/spec/api-gaps/calendar.md index b853b0953..df1a4025c 100644 --- a/spec/api-gaps/calendar.md +++ b/spec/api-gaps/calendar.md @@ -1,8 +1,9 @@ --- gap: calendar -status: no-json-contract +status: addressed-in-bc3-pr-12321 detected: 2026-05-01 sdk_demand: medium +bc3_pr: 12321 bc3_refs: introduced_in: five bc3_plan_phase: 3b @@ -18,46 +19,45 @@ bc3_refs: ## What's missing -BC5 introduces a top-level Calendar resource (a per-user calendar view, not the -per-project Schedule). The web app reads and updates calendar settings via -`calendars_controller.rb`, but there are no JSON-formatted responses on those -routes today. The BC3 parity plan Phase 3b ships **show + update only** — -not full CRUD. +SDK absorption only — the contract shipped via BC3 **#12321** in the BC5 API +train (2026-07-18..21). `doc/api/sections/calendars.md` on `master` is the +contract of record, documenting the planned **show + update only** scope +(not full CRUD): + +- `GET /calendars/:id.json` — returns the calendar (keyed by bucket id). +- `PUT /calendars/:id.json` — updates the calendar. + +The Calendar is a top-level BC5 resource (a calendar view distinct from the +per-project Schedule) with no BC4 analog, so additive coverage is safe. ## Why it matters Without `GET` an SDK client can't display the user's calendar surface in a -custom integration. Without `PUT` consumers can't set the few mutable -properties (e.g. visibility, default view) the web UI exposes. This is a new -top-level resource on BC5 with no BC4 analog, so additive coverage is safe. +custom integration. Without `PUT` consumers can't set the mutable properties +the web UI exposes. This is a new top-level resource on BC5 with no BC4 +analog, so additive coverage is safe. ## Suggested API shape -`GET /:account_id/calendars/:id.json`: -- `id` (long), `name` (string), `url`, `app_url` -- `view` ("day" | "week" | "month" | ...) and any other settings the form exposes -- `creator` (Person), `created_at`, `updated_at` -- `subscribed_buckets` (array of Bucket-shaped) if applicable - -`PUT /:account_id/calendars/:id.json`: -- Accept the mutable subset of the show payload -- Return `204 No Content` on success or the updated resource +Per the merged `doc/api/sections/calendars.md` — derive the exact field list +from the doc's captured examples at absorption time rather than restating it +here (the doc examples are regenerated from live BC5 by the #11629 tooling). ## Implementation notes for BC3 -- New `app/views/api/calendars/_calendar.json.jbuilder` partial; `show.json.jbuilder`, - `update.json.jbuilder` reuse it. -- Add `respond_to :json` (or per-action `format.json`) to the relevant actions - in `calendars_controller.rb`. -- Add `doc/api/sections/calendars.md` describing both routes. -- Re-evaluate `index/create/destroy` if usage signals demand later — initial - scope is intentionally small. +Shipped — nothing pending. `calendars_controller.rb` serves both routes with +JSON branches; `doc/api/sections/calendars.md` documents them. Re-evaluate +`index/create/destroy` only if usage signals demand later — the shipped scope +is intentionally small. ## SDK absorption plan when this lands -- New Smithy operations: `GetCalendar`, `UpdateCalendar`. -- New shapes: `Calendar`, `UpdateCalendarInput`/`Output`. -- New service registration: `CalendarsService` with `get(id)` and `update(id, input)`. +- New Smithy operations: `GetCalendar`, `UpdateCalendar`, with shapes derived + from the merged doc's examples. +- New service registration: `CalendarsService` with `get(id)` and + `update(id, input)`. +- Status flips to `absorbed-in-sdk` with the absorption PR (which adds the + Smithy refs). - Canary fixture: add `GetCalendar` to `live-my-surface.json` once a stable fixture-id resolution path exists (e.g., dock walk → calendar tool). - Pairwise check: BC4 absent → BC5 present is fine; the assertion is structural diff --git a/spec/api-gaps/everything-aggregates.md b/spec/api-gaps/everything-aggregates.md index e771788f9..419d1e1ba 100644 --- a/spec/api-gaps/everything-aggregates.md +++ b/spec/api-gaps/everything-aggregates.md @@ -1,8 +1,9 @@ --- gap: everything-aggregates -status: no-json-contract +status: addressed-in-bc3-pr-11627 detected: 2026-05-01 sdk_demand: high +bc3_pr: 11627 bc3_refs: introduced_in: five bc3_plan_phase: 3c @@ -18,7 +19,6 @@ bc3_refs: - "GET /:account_id/cards/unassigned.json" - "GET /:account_id/cards/no_due_date.json" - "GET /:account_id/cards/not_now.json" - - "GET /:account_id/documents.json" - "GET /:account_id/messages.json" - "GET /:account_id/comments.json" - "GET /:account_id/checkins.json" @@ -33,7 +33,6 @@ bc3_refs: - app/controllers/everything/boosts_controller.rb - app/controllers/everything/checkins_controller.rb - app/controllers/everything/forwards_controller.rb - - app/controllers/everything/documents_controller.rb - app/controllers/everything/files_controller.rb related_existing_api: - ListMyAssignments (similar contract — flat aggregate of one recording type) @@ -43,90 +42,86 @@ bc3_refs: ## What's missing -BC5 introduces account-wide flat listings of recordings by type, served by the -`everything/*_controller.rb` namespace under flat top-level paths (note: -`/everything/...` is the **Rails controller namespace**, not part of the URL). -There are 9 groups: todos, cards, messages, comments, boosts, checkins, -forwards, documents, and files. - -**Current BC3 scope: 18 documented GET operations across 9 groups**, per -`doc/api/sections/everything.md` in BC3 PR #10947 (open/unmerged, head -`589b1970`, verified 2026-07-13): todos ×5 and cards ×6 via filtered -sub-routes (`open`, `completed`, `overdue`, `unassigned`, `no_due_date`, plus -`not_now` for cards), and 7 root-path feeds — `/documents.json`, -`/messages.json`, `/comments.json`, `/checkins.json`, `/forwards.json`, -`/files.json`, `/boosts.json`. - -Superseded interim history: the launch reconciliation -(`BRIEF-bc5-reconciliation-scope-cuts.md`, `five+api` @ `716e710ee5`) briefly -scoped this to a 17-operation surface with the files group excluded, and -framed the feed groups as `recent`-suffixed sub-routes. #10947 has since -**restored the files group** (`GET /files.json`, with `kind` and -`people_ids[]` filters) -and documented the feed groups at their **root paths**. Only the bare -`/todos.json` and `/cards.json` roots remain HTML shells — use the filtered -sub-routes for those two groups. - -The `//recent.json` paths still exist in the web app as internal -Turbo-frame feeds, but they are **not** API surface. Per -`doc/api/sections/everything.md`: "those are internal: the root collection is -the documented API contract. Don't depend on the `/recent` paths." The SDK -must not model them. +SDK absorption only — the contract shipped. BC5's account-wide listings of +recordings by type are served by the `everything/*_controller.rb` namespace +under flat top-level paths (note: `/everything/...` is the **Rails controller +namespace**, not part of the URL). The contract merged to `master` via BC3 +**#11627** as part of the BC5 API train (2026-07-18..21); +`doc/api/sections/everything.md` on `master` is the contract of record. -## Why it matters +**Shipped scope: exactly 17 documented GET operations across 8 groups** +(re-derived from the merged doc's example markers): -Today, surfacing "all of one recording type across all projects" in a custom -integration requires walking projects and concatenating per-project listings. -The everything aggregates collapse that into a single paginated request. -This is a strong demand signal from the SDK side — the workaround is painful -and racy with project-membership changes. +- **todos ×5** — `/todos/{open,completed,overdue,unassigned,no_due_date}.json` +- **cards ×6** — `/cards/{open,completed,overdue,unassigned,no_due_date,not_now}.json` +- **flat roots ×6** — `/messages.json`, `/comments.json`, `/checkins.json`, + `/forwards.json`, `/files.json`, `/boosts.json` -## Suggested API shape +There is **no `/documents.json` root** — earlier drafts of this entry (working +from pre-merge #10947 heads) listed one, for an 18-op count. In the merged +contract, Basecamp documents surface through the `/files.json` feed instead, +alongside uploads and rich-text attachments. + +Two standing exclusions from the merged doc: -`doc/api/sections/everything.md` (#10947, head `589b1970`) documents two -contract families: +- The bare `/todos.json` and `/cards.json` roots are **not JSON** — they are + HTML shells in the web app. The filtered sub-routes are the JSON surface for + those two groups. +- `//recent.json` paths exist as internal web/Turbo-frame feeds and + are explicitly **not** API contract: "those are internal: the root + collection is the documented API contract. Don't depend on the `/recent` + paths." The SDK must never model them. -- **Bucket-grouped lists** — the todos/cards filter sub-routes (`/todos/open.json`, - `/cards/not_now.json`, …) return a paginated array of buckets, each grouping - the matching recordings (and their steps) under their parent project. -- **Flat recording lists** — `/todos/overdue.json`, `/cards/overdue.json`, and - the root feeds (`/documents.json`, `/messages.json`, `/comments.json`, - `/checkins.json`, `/forwards.json`, `/files.json`, `/boosts.json`) return a - flat array of recording objects, each embedding its `bucket` for project - context. Feeds are newest-first and paginated; overdue lists sort - oldest-first by due date. +## Why it matters + +Without these, surfacing "all of one recording type across all projects" in a +custom integration requires walking projects and concatenating per-project +listings. The everything aggregates collapse that into a single paginated +request. This is a strong demand signal from the SDK side — the workaround is +painful and racy with project-membership changes. -`GET /files.json` additionally takes `kind` (`all` | `images` | `pdfs` | -`documents` | `videos`) and repeatable `people_ids[]` query filters, and mixes -uploads with rich-text attachments (attachments wrapped in a recording -envelope plus `attachable_sgid` and blob metadata). +## Suggested API shape -Root feeds are documented at their root paths; todos and cards are reachable -only via the filtered sub-routes (their bare roots stay HTML shells). +The merged `doc/api/sections/everything.md` documents two contract families: + +- **Bucket-grouped lists** — the todo/card filter sub-routes + (`/todos/{open,completed,unassigned,no_due_date}.json` and + `/cards/{open,completed,unassigned,no_due_date,not_now}.json`) return a + **paginated array of buckets** (Link-header pagination; observed live at 5 + buckets per page), each entry grouping the matching recordings — and their + steps — under their parent project. +- **Flat recording lists** — `/todos/overdue.json` and `/cards/overdue.json` + return a flat array of overdue recordings sorted oldest-first by due date; + the 6 roots (`/messages.json`, `/comments.json`, `/checkins.json`, + `/forwards.json`, `/files.json`, `/boosts.json`) return flat, + recency-ordered (newest-first), paginated recording arrays, each item + embedding its `bucket` for project context. + +`GET /files.json` additionally takes `kind` +(`all` | `images` | `pdfs` | `documents` | `videos`) and repeatable +`people_ids[]` query filters, and mixes uploads, Basecamp documents, and +rich-text attachments (attachments wrapped in a recording envelope plus +`attachable_sgid` and blob metadata). ## Implementation notes for BC3 -- 9 controllers under `app/controllers/everything/` (the files group is back - in scope — see What's missing). -- Feed groups serve JSON at their root paths; the bare `/todos.json` and - `/cards.json` roots stay HTML shells with the filtered sub-routes as the - JSON surface. `//recent.json` stays an internal web Turbo-frame - feed — keep it out of `doc/api/`. -- Consistency: all 9 groups should use the same pagination + filter idiom. - Inconsistency between groups creates per-endpoint absorption work in the SDK. +Shipped — nothing pending. 8 controllers under `app/controllers/everything/` +serve the 17 operations. The bare `/todos.json` and `/cards.json` roots stay +HTML shells, and `//recent.json` stays internal web surface, per the +merged doc. ## SDK absorption plan when this lands -- New `EverythingService` with **18** operations matching the documented - contract (one per endpoint in the frontmatter route list), opening once BC3 - PR #10947 merges. Re-derive the operation list from - `doc/api/sections/everything.md` at the merge head at absorption time. -- Do **not** model the `//recent.json` aliases unless explicitly - approved — they are internal web feeds, not API contract. -- Each op routed at the flat top-level path (no `/everything/` URL prefix). -- Reuse existing recording shapes (`Todo`, `Card`, `Document`, `Message`, etc.). -- Canary fixture: cover at least one operation per group (9 groups) to catch +- New `EverythingService` with **17** operations matching the merged contract + (one per endpoint in the frontmatter route list). Re-verify the operation + list against `doc/api/sections/everything.md` at absorption time. +- Two response families: a bucket-grouped shape for the todo/card filter + sub-routes (bucket + grouped recordings + steps) and flat recording arrays + for the overdue lists and the 6 roots. Reuse existing recording shapes + (`Todo`, `Card`, `Message`, etc.) for the elements. +- Do **not** model the `//recent.json` aliases — internal web feeds, + not API contract. Do not model bare `/todos.json` / `/cards.json` (HTML + shells). +- Model the `/files.json` `kind` and `people_ids[]` query filters. +- Canary fixture: cover at least one operation per group (8 groups) to catch shape drift. Pairwise check: BC4 absent → BC5 present is fine. -- Operation count in the PR description must match what #10947 actually ships - (18 as of head `589b1970`, 2026-07-13); re-sync the route list if BC3 - settles a different final count before the absorption PR opens. diff --git a/spec/api-gaps/memories-emptied-regression.md b/spec/api-gaps/memories-emptied-regression.md index 7b210c371..94a85f40c 100644 --- a/spec/api-gaps/memories-emptied-regression.md +++ b/spec/api-gaps/memories-emptied-regression.md @@ -1,9 +1,9 @@ --- gap: memories-emptied-regression -status: partial-coverage +status: addressed-in-bc3-pr-11628 detected: 2026-05-27 sdk_demand: high -bc3_pr: 10947 +bc3_pr: 11628 bc3_refs: introduced_in: master routes: @@ -12,71 +12,93 @@ bc3_refs: - GetMyNotifications --- -# Memories emptied on BC5 (subtractive regression) +# Memories emptied on BC5 (subtractive delta, settled by contract) > **Not an additive gap.** Every other entry in this registry tracks *new* BC5 -> surface awaiting JSON coverage. This entry tracks a *subtractive regression*: -> a field BC4 populates that BC5 emptied. `status: partial-coverage` is the -> closest schema fit — read it as "coverage regressed," not "coverage partially -> added." Status flips to `addressed-in-bc3-pr-10947` when #10947 merges. +> surface awaiting JSON coverage. This entry tracked a *subtractive* delta — +> a field BC4 populates that BC5 emptied — and records how it settled: +> **permanently empty by documented contract**. `addressed-in-bc3-pr-11628` +> here means BC3 shipped the *documented contract for the empty field*, not a +> repopulation. ## What's missing -`GET /:account_id/my/readings.json` emits a top-level `memories` array. On BC4 -(the `four` branch) it is populated; on BC5 (`master`, production) it ships as -an unconditional empty array. Source diff of -`app/views/api/my/readings/index.json.jbuilder`: +Nothing anymore — the contract is settled. `GET /:account_id/my/readings.json` +emits a top-level `memories` array that is **permanently `[]` on BC5**. +`doc/api/sections/my_notifications.md` (language codified by BC3 **#11628**, +the BC4 wire-format compatibility PR in the BC5 API train) documents it +explicitly: `memories` "remains in the payload as an always-empty placeholder," +replaced by `bubble_ups` (capped at the 50 most recently read items, with +scheduling surfaced separately under `scheduled_bubble_ups`). -- BC4 (`four`): `json.memories @memories, partial: "my/readings/reading", as: :reading` - — **populated**. -- BC5 (`master`, production): `json.memories []` — **unconditional empty - array**, no account-gating. +History of the finding, kept as narrative: -The per-reading `memory_url` field is preserved on the wire; only the top-level -collection regressed. There is **no** "alias to `bubble_ups`" on production: -the once-assumed commit `64acf34` does not exist on `four`, `five`, or `master`. +- **2026-05-27 (regression discovery):** source diff of + `app/views/api/my/readings/index.json.jbuilder` showed BC4 (`four`) rendering + `json.memories @memories, partial: "my/readings/reading", as: :reading` + (populated) while BC5 (`master`, production) shipped `json.memories []` — + an unconditional empty array, no account-gating. The per-reading + `memory_url` field was preserved; only the top-level collection emptied. + There was never an "alias to `bubble_ups`" on production — the once-assumed + commit `64acf34` does not exist on `four`, `five`, or `master`. +- **Interim (fix-in-flight framing):** then-open BC3 PR #10947 carried a one-line + `json.memories @bubble_ups` alias that would have repopulated the collection. + This entry tracked that as the pending fix. +- **2026-07-18..21 (settled):** the BC5 API train shipped and **#10947 closed + unmerged**, superseded by the train. The alias never shipped and never will. + #11628 codified the always-empty placeholder language in + `doc/api/sections/my_notifications.md`. This is now a **permanent, accepted + BC4→BC5 subtractive delta**, no longer a pending regression. ## Why it matters -This is silent data loss for existing BC4 integrations: the request still -succeeds, the field is still present and still type-conformant (an array), so -per-backend schema validation passes on both sides. Only a *pairwise* BC4↔BC5 -comparison catches it — exactly the additive-only invariant the live canary -exists to enforce. It is the canonical demonstration of why per-backend schema -checks are necessary but not sufficient. +For existing BC4 integrations this remains a real behavior change: the request +still succeeds, the field is still present and still type-conformant (an +array), so per-backend schema validation passes on both sides. Only a +*pairwise* BC4↔BC5 comparison surfaces it — exactly the additive-only +invariant the live canary exists to enforce, and the canonical demonstration +of why per-backend schema checks are necessary but not sufficient. The delta +is now *documented and intentional*, which changes its classification (accepted +delta, not regression) but not its visibility to BC4-era readers. ## Suggested API shape -No new shape is needed, and the SDK proposes none: BC3 has already chosen the -fix. Open PR **#10947** (head `589b1970`, verified 2026-07-13) changes the -jbuilder to `json.memories @bubble_ups`, repopulating the top-level collection -from the Bubble Up successor so BC4 readers keep receiving a populated array. -This entry records that as the fix-in-flight rather than re-proposing a shape. +None — BC3 decided the shape by documentation: `memories` stays in the payload +as an always-empty placeholder, and `bubble_ups` / `scheduled_bubble_ups` are +the durable successors. The same doc page documents the adjacent bubble-up +surface the SDK should absorb next: + +- `bubble_ups_count` and `scheduled_bubble_ups_count` — top-level counts for + notification UI. +- `limit_bubble_ups` query param — `true` caps `bubble_ups` at 2 current + items and omits the `scheduled_bubble_ups` key (defaults to `false`). +- `GET /my/readings/bubble_ups.json` — a dedicated paginated list (50 items + per page) of current and scheduled bubble-ups. ## Implementation notes for BC3 -- The fix is one line and **already written** in open PR #10947 - (`json.memories @bubble_ups`). This is a merge/ship item, not a design item. -- Until #10947 merges, production `master` stays regressed. -- BC3 provenance at the time of this finding: `five+api` @ `716e710ee5` - (the reconciliation handoff, suite green). +None pending — BC3's side is done. `doc/api/sections/my_notifications.md` on +`master` is the contract of record: `memories` is an always-empty placeholder +superseded by `bubble_ups`. Any future repopulation of `memories` would be a +contract change requiring a doc update, at which point this entry gets a +follow-up. ## SDK absorption plan when this lands -- **Now (regression live):** the live-canary invariant for `memories` lives in - **PR #308**, not on this branch. That PR adds the - `pairwiseSupersetArray: ["memories"]` rule on `GetMyNotifications` plus a - temporary `pairwiseDeltaAllowed: ["memories"]` waiver (in - `conformance/tests/live-my-surface.json`, which exists on #308's branch) that - keeps the canary green on this known regression while still protecting every - other path. This branch carries the registry record only — no canary files — - and is what that waiver points back to. -- **Once PR #308 and BC3 #10947 have both landed:** remove the - `pairwiseDeltaAllowed: ["memories"]` waiver from - `conformance/tests/live-my-surface.json` (restoring the hard-fail), repin - `spec/api-provenance.json` past the fix, and flip this entry's `status` to - `addressed-in-bc3-pr-10947` (the `bc3_pr: 10947` field is already set). -- No **structural** Smithy change is required — `memories` is already modeled - on `GetMyNotificationsOutput`. (This PR does realign that field's doc comment - to describe the regression + the #10947 fix and regenerates the artifacts - that inherit it; that is documentation, not a shape change.) +- **Canary waiver is now permanent:** the live-canary invariant for `memories` + lives in **PR #308** (`conformance/tests/live-my-surface.json` on that + branch): a `pairwiseSupersetArray: ["memories"]` rule on `GetMyNotifications` + plus a `pairwiseDeltaAllowed: ["memories"]` waiver. With the contract + settled, that waiver is **permanent** — the machine-readable record of the + accepted BC4→BC5 delta, citing `doc/api/sections/my_notifications.md` and + BC3 #11628 just as this entry does. Retire it only if BC4 empties `memories` + (delta disappears) or BC5's documented contract changes (delta reopens). +- **Future absorption items** (separate additive PR, not this entry's + regression scope): `bubble_ups_count` / `scheduled_bubble_ups_count` fields + and the `limit_bubble_ups` query param on `GetMyNotifications`, plus a new + operation for `GET /my/readings/bubble_ups.json`. +- No **structural** Smithy change is required for `memories` itself — it is + already modeled on `GetMyNotificationsOutput`. Its doc comment describes the + settled contract (permanently empty on BC5; use `bubble_ups`); the artifacts + that inherit it are regenerated in this PR. New integrations must not rely + on `memories` being populated on BC5. diff --git a/spec/api-gaps/recordable-subtypes-doc.md b/spec/api-gaps/recordable-subtypes-doc.md index d27c81cf7..04b72a102 100644 --- a/spec/api-gaps/recordable-subtypes-doc.md +++ b/spec/api-gaps/recordable-subtypes-doc.md @@ -7,16 +7,15 @@ bc3_refs: introduced_in: five bc3_plan_phase: 3a routes: - - "POST /:account_id/buckets/:bucket_id/journals.json (create)" - - "POST /:account_id/buckets/:bucket_id/journals/:journal_id/entries.json (create)" - - "POST /:account_id/buckets/:bucket_id/cloud_files.json (create)" - - "POST /:account_id/buckets/:bucket_id/google_documents.json (create)" + - "POST /:account_id/buckets/:bucket_id/cloud_files.json (create — shipped)" + - "POST /:account_id/buckets/:bucket_id/google_documents.json (create — shipped)" + - "(Journal / Journal::Entry routes — NOT shipped; dropped from the BC5 API train)" - "(plus polymorphic surfacing in existing Recording responses — no new GET routes)" controllers: - - app/controllers/journals_controller.rb - - app/controllers/journals/entries_controller.rb - app/controllers/cloud_files_controller.rb - app/controllers/google_documents_controller.rb + - app/controllers/journals_controller.rb + - app/controllers/journals/entries_controller.rb related_existing_api: - GetRecording (polymorphic by recording_type) --- @@ -25,88 +24,65 @@ bc3_refs: ## What's missing -BC5 surfaces several Recording subtypes that have always existed in the -codebase but have never been formally documented as API-shaped: +Split outcome from the BC5 API train (2026-07-18..21), tracked in this single +brief (no split into per-subtype briefs): -- `Journal` and `Journal::Entry` (per-project journal posts) -- `CloudFile` (linked external file: Google Drive, Dropbox, etc.) -- `GoogleDocument` (specifically a Google Docs link, distinct from CloudFile) +- **Shipped:** `CloudFile` (linked external file: Google Drive, Dropbox, etc.) + and `GoogleDocument` (specifically a Google Docs link, distinct from + CloudFile) landed via BC3 **#12320** — `doc/api/sections/cloud_files.md` and + `doc/api/sections/google_documents.md` are on `master`. SDK absorption for + these two is pending. +- **Did NOT ship:** `Journal` and `Journal::Entry`. BC3 **#11629**'s "Drop + journal doc generation" commit removed the journal JSON API coverage from + the train — no product surface, no traffic. The journal routes remain + undocumented; treat them as out of API scope until BC3 revisits. -These appear polymorphically in existing Recording responses (search results, -activity feeds, etc.). Their wire shapes likely render via existing partials, -but the subtypes are absent from `doc/api/` and from the SDK's typed shapes. +The status stays `partial-coverage`: part of the subtype family has a merged +contract awaiting absorption, part has no JSON contract at all. -The BC3 plan Phase 3a covers documentation + Smithy shapes **and `create` -operations** for all four subtypes (Journal, Journal::Entry, CloudFile, -GoogleDocument). This is not shape/doc-only absorption — the SDK adds -`Create*` operations to match. - -**Door is excluded** by the BC3 plan from standalone API coverage and is -covered (if at all) only as a string-typed `type` value on existing -Recording responses. +**Door remains excluded** from standalone API coverage and is covered (if at +all) only as a string-typed `type` value on existing Recording responses. ## Why it matters -SDK consumers iterating through Recording-shaped responses (Search, -Activity, etc.) currently see opaque payloads when the underlying Recording -is one of these subtypes. Adding typed shapes lets them pattern-match on -`type` and render appropriately. +SDK consumers iterating through Recording-shaped responses (Search, Activity, +etc.) currently see opaque payloads when the underlying Recording is one of +these subtypes. Adding typed shapes lets them pattern-match on `type` and +render appropriately. ## Suggested API shape -Two parallel additions — documentation/typed shapes for read paths, and new -POST endpoints for create paths. - -**Read paths (already surface via polymorphism):** - -1. Document the wire shape for each subtype: - - `Journal`: id, title, status, urls, recordings_count, etc. (Recording-like) - - `Journal::Entry`: id, title, content, journal (parent reference) - - `CloudFile`: id, name, content_type, external_url, source ("Google Drive" etc.) - - `GoogleDocument`: id, name, google_url, doc_type -2. Confirm `type` value emitted by each (e.g. `"Journal::Entry"`, - `"CloudFile"`, `"GoogleDocument"`). -3. Verify polymorphic Recording-shaped responses include these without - manual whitelisting (`Recording#bubbleupable?` test should suffice). - -**Create paths (BC3 Phase 3a):** +For the shipped pair, follow the merged docs: -- `POST /:account_id/buckets/:bucket_id/journals.json` — input: `title`, - optional `description`/`status`. Returns the created Journal. -- `POST /:account_id/buckets/:bucket_id/journals/:journal_id/entries.json` - — input: `title`, `content`. Returns the created Journal::Entry. -- `POST /:account_id/buckets/:bucket_id/cloud_files.json` — input: external - URL, name, source. Returns the created CloudFile. -- `POST /:account_id/buckets/:bucket_id/google_documents.json` — input: - Google Doc URL, name. Returns the created GoogleDocument. +- `CloudFile` — per `doc/api/sections/cloud_files.md` (create route plus the + documented wire shape). +- `GoogleDocument` — per `doc/api/sections/google_documents.md` (create route + plus the documented wire shape). -Confirm the exact controller paths and input shapes against BC3 Phase 3a -before opening the absorption PR. +Confirm the exact `type` discriminator values against the merged docs' +captured examples at absorption time. No shape is proposed for Journal / +Journal::Entry — there is no contract to model. ## Implementation notes for BC3 -- Spot-check `app/views/api/journals/`, `app/views/api/cloud_files/`, - `app/views/api/google_documents/` — partials may already exist via - polymorphic dispatch. -- Add `doc/api/sections/journals.md`, etc. -- For create endpoints: confirm controller-level routes; existing - `journals_controller.rb`, `cloud_files_controller.rb`, etc. likely host - the `create` action with a JSON branch added in Phase 3a. +- CloudFile + GoogleDocument: shipped — nothing pending. +- Journal: if BC3 later gives journals a product surface and JSON contract, + that is net-new API work (routes, jbuilder views, doc section); this brief + then updates its scope. ## SDK absorption plan when this lands -- New Smithy structures: `Journal`, `JournalEntry`, `CloudFile`, - `GoogleDocument`. -- New Smithy operations: `CreateJournal`, `CreateJournalEntry`, - `CreateCloudFile`, `CreateGoogleDocument` — each with input/output - structures. -- New service registrations: likely `JournalsService`, `CloudFilesService`, - `GoogleDocumentsService` (consolidate if BC3 puts them under a single - controller namespace). -- Where Recording is polymorphic, extend the discriminated structure (or - add new union members) with the new `type` values. +- New Smithy structures: `CloudFile`, `GoogleDocument`; new operations: + `CreateCloudFile`, `CreateGoogleDocument` — inputs/outputs per the merged + docs. +- New service registrations: `CloudFilesService`, `GoogleDocumentsService` + (or consolidated, matching BC3's controller namespacing). +- No `Journal` / `JournalEntry` shapes or operations — dropped from the train; + do not model ahead of a BC3 contract. +- Where Recording is polymorphic, extend the discriminated structure with the + new `type` values. - Door is not modelled separately — appears as a string `type` value on existing Recording responses, undecoded. - Canary: extend the `Search` and `Activity` fixture coverage to include - recordings of these new types if test data permits. Create operations - not covered by canary (read-only canary scope). + recordings of these new types if test data permits. Create operations not + covered by canary (read-only canary scope). diff --git a/spec/api-gaps/recording-bubbleupable-field.md b/spec/api-gaps/recording-bubbleupable-field.md index f7101c5eb..16ed86655 100644 --- a/spec/api-gaps/recording-bubbleupable-field.md +++ b/spec/api-gaps/recording-bubbleupable-field.md @@ -22,6 +22,11 @@ Recording envelope to indicate whether the current user can bubble-up the recording. The BC3 plan Phase 3e covers this addition along with any other new envelope fields. +**Post-train spot-check (2026-07-21):** the BC5 API train (2026-07-18..21) +did not close this. `bubbleupable` appears **nowhere in `doc/api/`** on +`master` — no documented wire contract exists. Status stays +`no-json-contract`. + ## Why it matters Without the field, SDK consumers can't pre-compute the eligibility of UI diff --git a/spec/api-gaps/rich-text-project-attachable.md b/spec/api-gaps/rich-text-project-attachable.md index c6e2e2de8..1363aa733 100644 --- a/spec/api-gaps/rich-text-project-attachable.md +++ b/spec/api-gaps/rich-text-project-attachable.md @@ -19,8 +19,15 @@ bc3_refs: The existing `doc/api/sections/rich_text.md` enumerates Attachable types (types of recordings that can be inlined in a rich-text body). BC5 adds -`Project` to that list — you can now attach a project reference inside a -message, document, or comment body. +`Project` to that list in the product — you can attach a project reference +inside a message, document, or comment body — but the contract has not been +documented. + +**Post-train spot-check (2026-07-21):** the BC5 API train (2026-07-18..21) +did not close this. The merged `doc/api/sections/rich_text.md` on `master` +still carries **no Project-as-attachable contract** — the only project +reference in it is prose about Person mentions. Status stays +`no-json-contract`. ## Why it matters diff --git a/spec/api-gaps/scratchpad.md b/spec/api-gaps/scratchpad.md index 930f4ea5c..396789f69 100644 --- a/spec/api-gaps/scratchpad.md +++ b/spec/api-gaps/scratchpad.md @@ -1,67 +1,67 @@ --- gap: scratchpad -status: no-json-contract +status: addressed-in-bc3-pr-12322 detected: 2026-05-01 sdk_demand: medium +bc3_pr: 12322 bc3_refs: introduced_in: five bc3_plan_phase: 3b routes: - - "GET /:account_id/my/navigation/notes.json (URL pending — BC3 plan defers final path)" - - "PUT /:account_id/my/navigation/notes.json (URL pending)" + - "GET /:account_id/my/notes.json" + - "PUT /:account_id/my/notes.json" controllers: - - app/controllers/my/notes_controller.rb (path tentative — actual controller name pending BC3 decision) + - app/controllers/my/notes_controller.rb related_existing_api: [] --- -# Scratchpad (per-user notes) +# Scratchpad / My Notes (per-user note) ## What's missing -BC5 introduces a per-user "scratchpad" — a single rich-text note attached to -the navigation surface, distinct from the per-project Documents resource. -Read/write is exposed in the web UI but no JSON contract exists yet. +SDK absorption only — the contract shipped via BC3 **#12322** ("My Notes") in +the BC5 API train (2026-07-18..21). The once-deferred URL question is settled: +the shipped route is **`/my/notes.json`**, documented in +`doc/api/sections/my_notes.md` on `master`. -The BC3 parity plan Phase 3b ships the jbuilder partial and a JSON branch on -the relevant controller, but **the URL path is explicitly deferred** by the -BC3 team. The plan's working default is `/my/navigation/notes.json`; the -final path is BC3's call. This brief tracks the decision. +The resource is a per-person notebook — a single rich-text note that follows +the user across devices. The API treats it as a singleton at `/my/notes.json`: + +- `GET /my/notes.json` — returns the authenticated user's note. If the user + has not yet written anything, the shape is the same with empty `content` and + `null` `id` / `created_at` / `updated_at` — the record is created on first + update. +- `PUT /my/notes.json` — updates (or first-creates) the note. Saves are + versioned server-side; the API always returns the current note. + +The wire `type` is `Notebook::Note`. ## Why it matters External SDK consumers building dashboards or note-syncing tools can't read or -write the user's scratchpad without screen-scraping. As the navigation gets -more first-class data attached to it, leaving notes off the API surface is a +write the user's note without screen-scraping. As the navigation gets more +first-class data attached to it, leaving notes off the API surface is a predictable source of demand-signal complaints. ## Suggested API shape -`GET /:account_id/my/navigation/notes.json` (or final-decided path): -- `id` (long, optional — single per-user note may not need an id) -- `content` (rich text, format consistent with Document/Message bodies) -- `updated_at`, `created_at` - -`PUT /:account_id/my/navigation/notes.json`: -- `content` (string) -- Returns 204 or the updated payload. +Per the merged `doc/api/sections/my_notes.md`: `id`, `type` +(`"Notebook::Note"`), `content` (rich text), `created_at`, `updated_at` — +with the documented null-until-first-write behavior on `GET`. ## Implementation notes for BC3 -- Resolve URL path first. Coordinate with the BC3 plan owner; once the path - is fixed, update this brief's `routes:` field and the corresponding SDK - Smithy operation paths. -- Single-resource (not collection): the `index/show` distinction collapses - here — there's one note per user. -- Add to `doc/api/`. +Shipped — nothing pending. `my/notes_controller.rb` serves the singleton +routes and `doc/api/sections/my_notes.md` documents them. ## SDK absorption plan when this lands -- Smithy: `GetMyScratchpad`, `UpdateMyScratchpad` (or `MyNotesService` — - service name follows whatever BC3 picks for the URL). -- Service registration depends on URL: if `/my/notes.json`, group with the - `My*` services; if under `/my/navigation/...`, follow that namespacing. +- Smithy: `GetMyNote`, `UpdateMyNote` (singleton — no id path param), grouped + with the `My*` services per the shipped `/my/notes.json` path. +- Model the nullable `id` / `created_at` / `updated_at` on the GET response + (pre-first-write state). +- Status flips to `absorbed-in-sdk` with the absorption PR (which adds the + Smithy refs). - Canary fixture: a single per-account fixture-id-free GET works well since the resource is implicit-self. - Pairwise check: BC4 absent → BC5 present is fine. - -**Action item**: revisit this brief when BC3 commits the URL path. diff --git a/spec/api-gaps/search-filter-additions.md b/spec/api-gaps/search-filter-additions.md index 0d2f09447..6c0f774f8 100644 --- a/spec/api-gaps/search-filter-additions.md +++ b/spec/api-gaps/search-filter-additions.md @@ -1,6 +1,6 @@ --- gap: search-filter-additions -status: no-json-contract +status: partial-coverage detected: 2026-05-01 sdk_demand: medium bc3_refs: @@ -19,15 +19,18 @@ bc3_refs: ## What's missing -The existing Search endpoint accepts a small set of filter parameters today. -BC5 adds the following (per BC3 plan Phase 3e): +Docs shipped, params not final — **do not absorb yet**. Search filter +documentation landed on `master` with the BC5 API train (2026-07-18..21), but +open BC3 **#12361** (search params rework) is actively reshaping the filter +parameter surface. The status stays `partial-coverage` until #12361 settles: +absorbing the current param list would model a contract BC3 has already +queued for change. -- `type_names` (string[]) — filter to specific recording types. -- `creator_ids` (long[]) — filter to recordings authored by specific people. -- `bucket_ids` (long[]) — restrict to specific projects. -- `exclude_chat` (boolean) — drop chat messages from results. -- `file_type` (string) — filter file recordings by extension/type. -- `sort` (enum: "relevance" | "recency" | …) — control result ordering. +The filter families in play (subject to #12361's rework — re-derive the final +list from `doc/api/sections/search.md` once it merges): + +- recording-type filtering, creator/person filtering, project scoping, +- chat exclusion, file-type filtering, and result ordering. The `timelines/searches` route is the timeline-scoped variant; covered here since it shares the input shape. @@ -41,18 +44,23 @@ breaks if BC3 changes the param names). ## Suggested API shape -Additive parameters on the existing `SearchInput` shape — types per the list -above. Response shape is unchanged. +Additive parameters on the existing `SearchInput` shape, typed per whatever +`doc/api/sections/search.md` documents after #12361 merges. Response shape is +unchanged. ## Implementation notes for BC3 - All additions are query-string params handled server-side. No new controller actions, no new partials. -- `doc/api/sections/search.md` updates the parameter list. -- Document defaults explicitly (e.g. `sort: "relevance"`). +- #12361 (open) is the deciding PR for the final param names/semantics; + `doc/api/sections/search.md` follows it. +- Document defaults explicitly (e.g. the default sort). ## SDK absorption plan when this lands +- **Wait for BC3 #12361 to merge**, then re-derive the param list from the + merged `doc/api/sections/search.md` and flip this entry to + `addressed-in-bc3-pr-12361`. - Extend the existing Smithy `SearchInput` structure with the new optional fields (each annotated `@httpQuery`). - Same change applies to the timeline-search input if it's a separate diff --git a/spec/api-gaps/stack-doc-and-smithy.md b/spec/api-gaps/stack-doc-and-smithy.md index c6eb6954a..961787662 100644 --- a/spec/api-gaps/stack-doc-and-smithy.md +++ b/spec/api-gaps/stack-doc-and-smithy.md @@ -35,15 +35,23 @@ confirmed **web-only on both BC4 (`four`) and BC5 (`master`)**: Earlier drafts of this brief assumed BC3 Phase 3b would ship full Stack CRUD + list-collectables JSON. The reconciliation handoff (`BRIEF-bc5-reconciliation-scope-cuts.md`, `five+api` @ `716e710ee5`) withdrew -that: there is no public JSON Stack contract on either branch. +that: there is no public JSON Stack contract on either branch. The BC5 API +train (2026-07-18..21) confirmed the classification — no Stack section shipped +with it. + +**Post-launch update (2026-07-21):** the product has renamed Stacks to +**Folders**, and an API for them is being scoped server-side. The wire `type` +remains `Stack`. Neither the rename nor the scoping changes this entry today — +there is still no JSON contract to model. ## Why it matters It matters as a *negative* result. The SDK explicitly does **not** model a `StacksService`, and the canary does not expect Stack endpoints on either backend. Recording that here prevents a future detector run or contributor from -re-filing Stacks as an additive gap. If Stacks ever gain a JSON contract, file -a fresh additive brief then. +re-filing Stacks as an additive gap. When the server-side Folders API scoping +produces a JSON contract, file a fresh additive brief then (watch the wire +`type`, which stays `Stack` despite the product rename). ## Suggested API shape @@ -54,10 +62,10 @@ propose. ## Implementation notes for BC3 -No SDK-facing action. If BC3 later decides to expose Stacks over JSON, that is -net-new API work: `respond_to :json` branches, standalone jbuilder views, a -`doc/api/sections/stacks.md`, and (if collectables are exposed) a -`Stacks::CollectablesController`. None of that exists today on either branch. +No SDK-facing action yet. The Folders API being scoped server-side is net-new +API work: `respond_to :json` branches, standalone jbuilder views, a `doc/api/` +section, and whatever collectables surface it defines. None of that exists +today on either branch. ## SDK absorption plan when this lands diff --git a/spec/api-gaps/step-top-level.md b/spec/api-gaps/step-top-level.md index 7393f1d2b..e7ef8f44c 100644 --- a/spec/api-gaps/step-top-level.md +++ b/spec/api-gaps/step-top-level.md @@ -1,14 +1,24 @@ --- gap: step-top-level -status: partial-coverage +status: absorbed-in-sdk detected: 2026-05-01 sdk_demand: low +bc3_pr: 12323 +smithy_refs: + - "GetCardStep (spec/basecamp.smithy:4456)" + - "CreateCardStep (spec/basecamp.smithy:4479)" + - "UpdateCardStep (spec/basecamp.smithy:4511)" + - "SetCardStepCompletion (spec/basecamp.smithy:4541)" + - "RepositionCardStep (spec/basecamp.smithy:4569)" bc3_refs: introduced_in: five bc3_plan_phase: 3b routes: - - "GET /:account_id/buckets/:bucket_id/cards/:card_id/steps/:id.json (existing — already in SDK)" - - "Top-level Step paths (final path/depth pending BC3 doc decision)" + - "GET /:account_id/card_tables/steps/:id.json (SDK-modeled; served but not listed in card_table_steps.md)" + - "POST /:account_id/card_tables/cards/:card_id/steps.json" + - "PUT /:account_id/card_tables/steps/:id.json" + - "PUT /:account_id/card_tables/steps/:id/completions.json" + - "POST /:account_id/card_tables/cards/:card_id/positions.json" controllers: - app/controllers/steps_controller.rb related_existing_api: @@ -23,46 +33,47 @@ bc3_refs: ## What's missing -BC5 generalises Step beyond the Kanban-card context (`Step::FormerlyKanbanStep` -keeps `type: "Kanban::Step"` on the wire — see SDK plan §Out of scope). The -jbuilder partial for Step is already shipped via the cards routes. The BC3 -parity plan Phase 3b adds a doc-only entry exposing top-level Step routes -(unscoped from cards). +Nothing — **absorbed**. The merged `doc/api/sections/card_table_steps.md` on +`master` (docs true-up, BC3 **#12323**) documents the top-level step routes — +`POST /card_tables/cards/:id/steps.json`, `PUT /card_tables/steps/:id.json`, +`PUT /card_tables/steps/:id/completions.json`, +`POST /card_tables/cards/:id/positions.json` (plus bucket-scoped +equivalents) — and the SDK already models all five top-level operations in +`spec/basecamp.smithy`: `GetCardStep` (:4456), `CreateCardStep` (:4479), +`UpdateCardStep` (:4511), `SetCardStepCompletion` (:4541), +`RepositionCardStep` (:4569). -The wire shape itself is unchanged — the SDK already models `CardStep`. What's -missing is documentation + Smithy operations under the new top-level paths. +The parameter check passes too: both `CreateCardStep` and `UpdateCardStep` +inputs carry `due_on: ISO8601Date` and `assignee_ids: PersonIdList`, matching +the merged doc. The doc's legacy `assignees` comma-separated-string param is +**deliberately unmodeled** in favor of the typed `assignee_ids` array. ## Why it matters -If BC3 documents top-level Step routes, the SDK needs corresponding Smithy ops -so SDK consumers can use them without manually constructing URLs from -recording-id pairs. Forward compat is fine: the existing `CardStepsService` -keeps working under its current paths. +Historical: BC5 generalised Step beyond the Kanban-card context +(`Step::FormerlyKanbanStep` keeps `type: "Kanban::Step"` on the wire), and +this brief tracked whether the SDK's operations would line up with the +top-level paths BC3 documented. They do — no URL construction from +recording-id pairs is needed by SDK consumers. ## Suggested API shape -Same as existing `CardStep` shape (already modelled at `spec/basecamp.smithy` -line 4712). The new operations are merely routed differently — most likely: - -- `GET /:account_id/steps/:id.json` (single Step regardless of recording parent) -- `PUT /:account_id/steps/:id/completions.json` (toggle completion) - -The wire payload's `type` field stays `"Kanban::Step"` per BC3 plan's -`Step::FormerlyKanbanStep` override. +Shipped and modeled; see the Smithy refs above. The wire payload's `type` +field stays `"Kanban::Step"` per the `Step::FormerlyKanbanStep` override. ## Implementation notes for BC3 -- Choose and document the canonical top-level path. -- Reuse the existing `_step.json.jbuilder` partial. -- Update `doc/api/sections/cards.md` (or add `doc/api/sections/steps.md`) to - describe both forms. +None — `doc/api/sections/card_table_steps.md` is the contract of record and +matches the SDK's modeled operations. ## SDK absorption plan when this lands -- Either extend `CardStepsService` with a new `getStep(id)` op routed at - `/:account_id/steps/:id.json`, OR add a parallel `StepsService` with the - same shape — coordinate naming with the BC3 doc choice. -- Do not rename `CardStepsService` — the existing service stays. -- No new Smithy structures (existing `CardStep` is reused). +Done — no further absorption work: + +- The five top-level operations exist with the documented paths and inputs. +- `CardStepsService` keeps its name; no parallel `StepsService` is needed. +- No new Smithy structures (the existing `CardStep` shape is reused). +- The legacy `assignees` comma-string param stays unmodeled by design; if BC3 + ever removes it from the doc, nothing changes here. - Canary fixture: optional; the existing CardStep coverage already exercises the wire shape. diff --git a/spec/api-gaps/todoset-completed-list-visibility.md b/spec/api-gaps/todoset-completed-list-visibility.md index fe55fb5d1..61b5a9992 100644 --- a/spec/api-gaps/todoset-completed-list-visibility.md +++ b/spec/api-gaps/todoset-completed-list-visibility.md @@ -22,10 +22,13 @@ A new route `todosets/completed_list_visibility` was detected on `origin/five` and is likely a PUT endpoint to show/hide the completed-todos list at the todoset level (matches the existing UI toggle). -**This entry is currently `ambiguous`**: the BC3 plan has not classified -whether this is an API-shaped resource or UI-only state. Classification -must happen on the BC3 side (the SDK can't determine the right answer -unilaterally); the brief stays in the registry either way: +**This entry is currently `ambiguous`**: BC3 has not classified whether this +is an API-shaped resource or UI-only state, and the BC5 API train +(2026-07-18..21) did not touch it. The deciding PR is open BC3 **#12021** +(completed-todos JSON), which covers the completed-list surface; its outcome +classifies this route. Classification must happen on the BC3 side (the SDK +can't determine the right answer unilaterally); the brief stays in the +registry either way: - If JSON-API: SDK absorbs as `UpdateTodosetCompletedListVisibility` op, this entry flips to `no-json-contract` and the absorption PR ships once @@ -53,6 +56,8 @@ The right answer comes from BC3, not from the SDK. Hence the brief. - Confirm whether the route is reached only by the web form (CSRF-token, session-cookie auth) or also by API auth. +- Open #12021 (completed-todos JSON) is the natural home for that call — it + decides how the completed-todos surface is exposed over JSON. - If API-shaped: add JSON branch + jbuilder + doc entry. - If UI-only: explicit note in BC3 plan would close this brief. From 1d45eec7e42c0662bc5fa2a997795a9058ff89f2 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 21 Jul 2026 19:57:22 -0700 Subject: [PATCH 8/9] spec: settle memories doc comment on the documented contract + regenerate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite GetMyNotificationsOutput.memories' doc comment to the settled contract: permanently [] on BC5 per doc/api/sections/my_notifications.md (codified by BC3 #11628 — an always-empty placeholder superseded by bubble_ups); BC4 still populates it, an accepted BC4->BC5 subtractive delta recorded in spec/api-gaps/memories-emptied-regression.md. New integrations should use bubble_ups / scheduled_bubble_ups. Regenerate every artifact inheriting the doc string (openapi.json, openapi-stripped, TS schema.d.ts + metadata, Go client.gen.go, Ruby types + metadata). --- go/pkg/generated/client.gen.go | 15 +++++++-------- openapi.json | 2 +- ruby/lib/basecamp/generated/metadata.json | 2 +- ruby/lib/basecamp/generated/types.rb | 2 +- spec/basecamp.smithy | 15 +++++++-------- typescript/src/generated/metadata.ts | 2 +- typescript/src/generated/openapi-stripped.json | 2 +- typescript/src/generated/schema.d.ts | 15 +++++++-------- 8 files changed, 26 insertions(+), 29 deletions(-) diff --git a/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index ceb87b237..dfa83a262 100644 --- a/go/pkg/generated/client.gen.go +++ b/go/pkg/generated/client.gen.go @@ -1026,14 +1026,13 @@ type GetMyNotificationsResponseContent struct { // `scheduled_bubble_ups` for the time-deferred subset. BubbleUps []Notification `json:"bubble_ups,omitempty"` - // Memories Legacy "save forever" collection. On BC5 `master` this currently ships as - // `[]` while BC4 (the `four` branch) still populates it — a confirmed - // subtractive regression tracked in - // `spec/api-gaps/memories-emptied-regression.md`. The fix is written but - // unmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New - // integrations should prefer `bubble_ups` (the durable successor, with - // optional scheduling via `scheduled_bubble_ups`); until #10947 ships, do - // not rely on `memories` being populated on BC5. + // Memories Legacy "save forever" collection. Permanently `[]` on BC5 by documented + // contract (`doc/api/sections/my_notifications.md`, codified by BC3 #11628): + // an always-empty placeholder superseded by `bubble_ups`. BC4 (the `four` + // branch) still populates it — an accepted BC4→BC5 subtractive delta + // recorded in `spec/api-gaps/memories-emptied-regression.md`. New + // integrations should use `bubble_ups` / `scheduled_bubble_ups` and must + // not rely on `memories` on BC5. Memories []Notification `json:"memories,omitempty"` Reads []Notification `json:"reads,omitempty"` diff --git a/openapi.json b/openapi.json index c6c3c146f..44ca048eb 100644 --- a/openapi.json +++ b/openapi.json @@ -23585,7 +23585,7 @@ "items": { "$ref": "#/components/schemas/Notification" }, - "description": "Legacy \"save forever\" collection. On BC5 `master` this currently ships as\n`[]` while BC4 (the `four` branch) still populates it — a confirmed\nsubtractive regression tracked in\n`spec/api-gaps/memories-emptied-regression.md`. The fix is written but\nunmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New\nintegrations should prefer `bubble_ups` (the durable successor, with\noptional scheduling via `scheduled_bubble_ups`); until #10947 ships, do\nnot rely on `memories` being populated on BC5." + "description": "Legacy \"save forever\" collection. Permanently `[]` on BC5 by documented\ncontract (`doc/api/sections/my_notifications.md`, codified by BC3 #11628):\nan always-empty placeholder superseded by `bubble_ups`. BC4 (the `four`\nbranch) still populates it — an accepted BC4→BC5 subtractive delta\nrecorded in `spec/api-gaps/memories-emptied-regression.md`. New\nintegrations should use `bubble_ups` / `scheduled_bubble_ups` and must\nnot rely on `memories` on BC5." }, "bubble_ups": { "type": "array", diff --git a/ruby/lib/basecamp/generated/metadata.json b/ruby/lib/basecamp/generated/metadata.json index fb46a0779..8125c2e6b 100644 --- a/ruby/lib/basecamp/generated/metadata.json +++ b/ruby/lib/basecamp/generated/metadata.json @@ -1,7 +1,7 @@ { "$schema": "https://basecamp.com/schemas/sdk-metadata.json", "version": "1.0.0", - "generated": "2026-07-22T02:19:46Z", + "generated": "2026-07-22T02:53:58Z", "operations": { "GetAccount": { "retry": { diff --git a/ruby/lib/basecamp/generated/types.rb b/ruby/lib/basecamp/generated/types.rb index e0250f567..7f048b2f5 100644 --- a/ruby/lib/basecamp/generated/types.rb +++ b/ruby/lib/basecamp/generated/types.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Auto-generated from OpenAPI spec. Do not edit manually. -# Generated: 2026-07-22T02:19:46Z +# Generated: 2026-07-22T02:53:58Z require "json" require "time" diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index e883f2201..4f6b03451 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -8188,14 +8188,13 @@ structure GetMyNotificationsOutput { unreads: NotificationList reads: NotificationList - /// Legacy "save forever" collection. On BC5 `master` this currently ships as - /// `[]` while BC4 (the `four` branch) still populates it — a confirmed - /// subtractive regression tracked in - /// `spec/api-gaps/memories-emptied-regression.md`. The fix is written but - /// unmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New - /// integrations should prefer `bubble_ups` (the durable successor, with - /// optional scheduling via `scheduled_bubble_ups`); until #10947 ships, do - /// not rely on `memories` being populated on BC5. + /// Legacy "save forever" collection. Permanently `[]` on BC5 by documented + /// contract (`doc/api/sections/my_notifications.md`, codified by BC3 #11628): + /// an always-empty placeholder superseded by `bubble_ups`. BC4 (the `four` + /// branch) still populates it — an accepted BC4→BC5 subtractive delta + /// recorded in `spec/api-gaps/memories-emptied-regression.md`. New + /// integrations should use `bubble_ups` / `scheduled_bubble_ups` and must + /// not rely on `memories` on BC5. memories: NotificationList /// Items the user has saved with Bubble Up (BC5 addition). Roughly the diff --git a/typescript/src/generated/metadata.ts b/typescript/src/generated/metadata.ts index 785317541..35de25db7 100644 --- a/typescript/src/generated/metadata.ts +++ b/typescript/src/generated/metadata.ts @@ -37,7 +37,7 @@ export interface MetadataOutput { const metadata: MetadataOutput = { "$schema": "https://basecamp.com/schemas/sdk-metadata.json", "version": "1.0.0", - "generated": "2026-07-22T02:19:44.980Z", + "generated": "2026-07-22T02:53:56.174Z", "operations": { "GetAccount": { "retry": { diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 6160bb0dd..28a0e1ab2 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -21261,7 +21261,7 @@ "items": { "$ref": "#/components/schemas/Notification" }, - "description": "Legacy \"save forever\" collection. On BC5 `master` this currently ships as\n`[]` while BC4 (the `four` branch) still populates it — a confirmed\nsubtractive regression tracked in\n`spec/api-gaps/memories-emptied-regression.md`. The fix is written but\nunmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New\nintegrations should prefer `bubble_ups` (the durable successor, with\noptional scheduling via `scheduled_bubble_ups`); until #10947 ships, do\nnot rely on `memories` being populated on BC5." + "description": "Legacy \"save forever\" collection. Permanently `[]` on BC5 by documented\ncontract (`doc/api/sections/my_notifications.md`, codified by BC3 #11628):\nan always-empty placeholder superseded by `bubble_ups`. BC4 (the `four`\nbranch) still populates it — an accepted BC4→BC5 subtractive delta\nrecorded in `spec/api-gaps/memories-emptied-regression.md`. New\nintegrations should use `bubble_ups` / `scheduled_bubble_ups` and must\nnot rely on `memories` on BC5." }, "bubble_ups": { "type": "array", diff --git a/typescript/src/generated/schema.d.ts b/typescript/src/generated/schema.d.ts index 66a71417a..b2a4fff27 100644 --- a/typescript/src/generated/schema.d.ts +++ b/typescript/src/generated/schema.d.ts @@ -3300,14 +3300,13 @@ export interface components { unreads?: components["schemas"]["Notification"][]; reads?: components["schemas"]["Notification"][]; /** - * @description Legacy "save forever" collection. On BC5 `master` this currently ships as - * `[]` while BC4 (the `four` branch) still populates it — a confirmed - * subtractive regression tracked in - * `spec/api-gaps/memories-emptied-regression.md`. The fix is written but - * unmerged: BC3 #10947 repopulates it via `json.memories @bubble_ups`. New - * integrations should prefer `bubble_ups` (the durable successor, with - * optional scheduling via `scheduled_bubble_ups`); until #10947 ships, do - * not rely on `memories` being populated on BC5. + * @description Legacy "save forever" collection. Permanently `[]` on BC5 by documented + * contract (`doc/api/sections/my_notifications.md`, codified by BC3 #11628): + * an always-empty placeholder superseded by `bubble_ups`. BC4 (the `four` + * branch) still populates it — an accepted BC4→BC5 subtractive delta + * recorded in `spec/api-gaps/memories-emptied-regression.md`. New + * integrations should use `bubble_ups` / `scheduled_bubble_ups` and must + * not rely on `memories` on BC5. */ memories?: components["schemas"]["Notification"][]; /** From 87f6bae21c1a0508b2897c333ec75322e3533e39 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 21 Jul 2026 21:53:04 -0700 Subject: [PATCH 9/9] api-gaps: record why bucket-scoped step aliases stay unmodeled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The step-top-level brief flipped to absorbed-in-sdk citing the five flat Smithy operations, but the merged card_table_steps.md also documents bucket-scoped equivalents. Make the exclusion explicit: the doc lists them as cross-references to the same operations, and the SDK follows the card-tables convention of modeling the canonical flat routes only — server-side aliases, not additional surface. --- spec/api-gaps/step-top-level.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/api-gaps/step-top-level.md b/spec/api-gaps/step-top-level.md index e7ef8f44c..36fb5a61c 100644 --- a/spec/api-gaps/step-top-level.md +++ b/spec/api-gaps/step-top-level.md @@ -38,7 +38,12 @@ Nothing — **absorbed**. The merged `doc/api/sections/card_table_steps.md` on `POST /card_tables/cards/:id/steps.json`, `PUT /card_tables/steps/:id.json`, `PUT /card_tables/steps/:id/completions.json`, `POST /card_tables/cards/:id/positions.json` (plus bucket-scoped -equivalents) — and the SDK already models all five top-level operations in +equivalents, e.g. `POST /buckets/:bucket_id/card_tables/cards/:id/steps.json` — +the doc lists these as cross-references to the same operations, and they are +**deliberately not modeled**: the SDK follows the card-tables service +convention of modeling the canonical flat `/card_tables/...` routes only; +the bucket-scoped forms are server-side aliases of the identical operations, +not additional API surface) — and the SDK already models all five top-level operations in `spec/basecamp.smithy`: `GetCardStep` (:4456), `CreateCardStep` (:4479), `UpdateCardStep` (:4511), `SetCardStepCompletion` (:4541), `RepositionCardStep` (:4569).