diff --git a/COORDINATION.md b/COORDINATION.md index bf4cca352..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,25 +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 that need product/API leadership decisions: +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` — 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. +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` — 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` — 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/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index 52422dc81..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. 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. 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 9dc62c7f4..44ca048eb 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. 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/api-gaps/README.md b/spec/api-gaps/README.md index 773ee00b8..ad8ff9dab 100644 --- a/spec/api-gaps/README.md +++ b/spec/api-gaps/README.md @@ -36,17 +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) | partial-coverage | 3b | medium | -| [search-filter-additions](search-filter-additions.md) | no-json-contract | 3e | medium | +| [stack-doc-and-smithy](stack-doc-and-smithy.md) | confirmed-not-api-resource | 3b | 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) | 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 125ff58a5..fb526f2d2 100644 --- a/spec/api-gaps/activity-timeline.md +++ b/spec/api-gaps/activity-timeline.md @@ -1,70 +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. + +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 ba7ebc8ba..419d1e1ba 100644 --- a/spec/api-gaps/everything-aggregates.md +++ b/spec/api-gaps/everything-aggregates.md @@ -1,34 +1,39 @@ --- 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 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/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)" + - "GET /:account_id/files.json" + - "GET /:account_id/boosts.json" 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 - 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) --- @@ -37,55 +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 recording-type groups: todos, cards, files, messages, comments, -boosts, checkins, forwards, documents. +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. + +**Shipped scope: exactly 17 documented GET operations across 8 groups** +(re-derived from the merged doc's example markers): + +- **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` + +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: -**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. +- 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. ## Why it matters -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. +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. ## 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. +The merged `doc/api/sections/everything.md` documents two contract families: -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`). +- **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. -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`. +`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/` 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/`. -- 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 one op per endpoint BC3 ships. -- 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. +- 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. diff --git a/spec/api-gaps/memories-emptied-regression.md b/spec/api-gaps/memories-emptied-regression.md new file mode 100644 index 000000000..94a85f40c --- /dev/null +++ b/spec/api-gaps/memories-emptied-regression.md @@ -0,0 +1,104 @@ +--- +gap: memories-emptied-regression +status: addressed-in-bc3-pr-11628 +detected: 2026-05-27 +sdk_demand: high +bc3_pr: 11628 +bc3_refs: + introduced_in: master + routes: + - "GET /:account_id/my/readings.json" + related_existing_api: + - GetMyNotifications +--- + +# 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 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 + +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`). + +History of the finding, kept as narrative: + +- **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 + +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 + +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 + +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 + +- **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 20d5e9676..961787662 100644 --- a/spec/api-gaps/stack-doc-and-smithy.md +++ b/spec/api-gaps/stack-doc-and-smithy.md @@ -1,75 +1,76 @@ --- 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. The BC5 API +train (2026-07-18..21) confirmed the classification — no Stack section shipped +with it. -`GET /:account_id/stacks/:id.json`: -- `id`, `title`, `position`, `created_at`, `updated_at` -- `creator` (Person) -- `collectables_count`, `collectables_url` +**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. -`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. 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). -`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 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 -- 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. diff --git a/spec/api-gaps/step-top-level.md b/spec/api-gaps/step-top-level.md index 7393f1d2b..36fb5a61c 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,52 @@ 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, 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). -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. diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index 1d742ee0d..4f6b03451 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -8188,14 +8188,13 @@ 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. 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 321696a30..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. 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. 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 f124dd366..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. 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. 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"][]; /**