Skip to content

Up Next priority writes + provenance repin to the exact-target contract (#12380, #12483) - #528

Merged
jeremy merged 6 commits into
mainfrom
c4-priorities
Jul 31, 2026
Merged

Up Next priority writes + provenance repin to the exact-target contract (#12380, #12483)#528
jeremy merged 6 commits into
mainfrom
c4-priorities

Conversation

@jeremy

@jeremy jeremy commented Jul 31, 2026

Copy link
Copy Markdown
Member

C4 — the sprint-closing absorption, sequenced last on purpose: it carries the mandatory provenance repin past bc3 #12483.

Why last

DeprioritizeAssignment could not be honestly classified idempotent as BC3 shipped it: the JSON DELETE inherited the web UI's card→step fallback, so a retried DELETE whose first attempt committed cascaded to a child step and deleted a second, different priority. The upstream fix (bc3 #12483, exact-target + no-op-on-absent, with the card+child-step repeat-DELETE regression) merged today; this PR absorbs the surface and repins to it. The bc-api mirror sync is up as bc-api#428.

What

  • Spec (+3 ops, 235 → 238, on the existing MyAssignments tag): PrioritizeAssignment (POST {id}, 204, idempotent — verified against the model: Assignment#prioritize is priority || create_priority!, a repeat is a pure no-op), DeprioritizeAssignment (DELETE, 204, idempotent only because of #12483 — say so in its doc comment), ReorderUpNext (POST {source_id, position}, 204, deliberately not retry-gated — a positional move's meaning shifts as the list changes — with #12380's hardened contract: typed 400s, flat-{error} 422s, bare bodyless 404).
  • Repin: e83b2733 → d0edc1283b with full drift triage in the commit message — the only doc/api or routes change in the entire range is the contract this PR absorbs; everything else is wire-neutral internals. provenance-sync propagates the 2026-07-31 API version date into every SDK's constants.
  • Wiring: Go wrapper methods (Reorder validates the 1-based position client-side); Python's sync and async clients gain the previously missing my_assignments accessor — the generated service was unreachable since the retrieval side shipped (the classic sync-only-wiring miss, surfaced by this PR's tests).
  • Registry: the gap file's original "DELETE is idempotent" claim is corrected on the record (wrong as-shipped; true only past #12483) and the entry flips to absorbed-in-sdk.
  • Conformance: 3 paths cases, idempotent-retry cases for both flagged ops (Ruby GET-only skips), and a ReorderUpNext no-retry case (503 → exactly one request) pinning the non-idempotent classification; dispatch in all five runners.
  • Counts: 238 / 122 / 77 / 199 — the sprint's planned BC3-fixed end state, matching behavior-model exactly. Sprint closes at 8 of 9 absorbed; schedule-recurrence-writes stays honestly addressed-in-bc3-pr-12359 behind its human-credentialed live probe.

Verification

Full make green (exit 0) + make kt-check-generated-drift clean; 22 conformance passes for the new ops + the two justified Ruby skips; TS 8/8, Ruby 7/7, Python 8/8 unit tests.


Summary by cubic

Adds Up Next write APIs to My Assignments—prioritize, exact-target deprioritize, and reorder—repins to the fixed #12483 contract, and models ReorderUpNext’s 404 as bodyless to match the server. Bumps API version to 2026-07-31.

  • New Features

    • PrioritizeAssignment (POST /my/priorities.json with {id}): returns 204, idempotent, retries on 429/503.
    • DeprioritizeAssignment (DELETE /my/priorities/{recordingId}): exact-target per #12483, returns 204, idempotent, retries on 429/503.
    • ReorderUpNext (POST /my/priority_moves.json with {source_id, position}): returns 204, not idempotent (no retries); typed 400, flat {error} 422, and 404 modeled without a body to reflect the wire. Go validates 1-based position.
    • Python sync and async clients gain the missing my_assignments accessor.
    • Metadata updated across SDKs (behavior-model.json and generated files): 238 ops total; 77 idempotent mutations. Conformance/unit tests cover paths, retries, and the no-retry pin for reorder.
  • Dependencies

    • Provenance repinned to the exact-target contract (bc3 d0edc1283b); API version set to 2026-07-31 in Go, Kotlin, Python, Ruby, Swift, and TypeScript.

Written for commit 97eef8a. Summary will update on new commits.

Review in cubic

jeremy added 5 commits July 31, 2026 08:41
BC3 #12380 documented the three write operations behind My Assignments'
Up Next list; the SDK modeled only the retrieval side. All three land
on the existing MyAssignments tag: PrioritizeAssignment (POST
/my/priorities.json {id}, 204, idempotent — Assignment#prioritize is
priority || create_priority!, a repeat is a pure no-op),
DeprioritizeAssignment (DELETE /my/priorities/{recordingId}, 204,
idempotent only because BC3 #12483 made the JSON path exact-target with
delete-of-absent as a no-op), and ReorderUpNext (POST
/my/priority_moves.json {source_id, position}, 204, deliberately NOT
retry-gated: a positional move's meaning shifts as the list changes,
with the typed 400 / flat-{error} 422 / bare 404 contract from #12380's
hardening). 235 -> 238 operations.
The pin moves e83b2733 -> d0edc1283b (bc3 #12483's merge). Full drift
triage of the range: #12478 test-DB seeding, #12479 scrape-time gauges,
#12480 boost-feed render preload, #12481 backpressure config removal,
a completion-lock race fix, relay-revocation ops tooling, #12444 bcenv
retirement — all wire-neutral internals; the only doc/api or routes
change in the entire range is my_assignments.md's exact-target
Deprioritize contract, absorbed by this PR. provenance-sync propagated
the API version date (2026-07-31) into every SDK's version constants.
Go's MyAssignmentsService gains the three write methods (Reorder
validates the 1-based position client-side and is never retried).
Python's sync AND async clients gain the my_assignments accessor —
it was never wired when the retrieval side shipped, so the generated
service was unreachable; the classic sync-only-wiring miss, caught by
this PR's tests.
TS/Ruby/Python pin the exact wire bodies ({id}, {source_id, position}),
the exact-target DELETE path, the typed 400/422 flat-{error} bodies,
and the bare bodyless 404. Conformance: three paths cases, idempotent-
retry cases for Prioritize (POST 503->204) and Deprioritize (DELETE
503->204) with Ruby's GET-only skips, and a ReorderUpNext no-retry case
(503 -> exactly one request) pinning the deliberate non-idempotent
classification. Dispatch in all five runners.
The sprint's BC3-fixed end state (238/122/77/199), matching
behavior-model exactly. Parity 75->77 / 197->199; SPEC ceiling
narrative + Appendix E; SECURITY 23 DELETEs / 7 flagged POSTs / 39
other POSTs. The gap file's original claim that the DELETE was
idempotent as-shipped is corrected on the record — it was wrong until
bc3 #12483 — and the entry flips to absorbed-in-sdk with three
smithy_refs, closing the sprint at 8 of 9 (schedule-recurrence-writes
stays honestly gated on its live probe).
Copilot AI review requested due to automatic review settings July 31, 2026 15:43
@github-actions github-actions Bot added documentation Improvements or additions to documentation typescript Pull requests that update TypeScript code ruby Pull requests that update the Ruby SDK go kotlin swift spec Changes to the Smithy spec or OpenAPI conformance Conformance test suite python Pull requests that update the Python SDK labels Jul 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1eff23971

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread spec/basecamp.smithy Outdated
Codex caught the mismatch (the C8 FieldValidationError catch's sibling):
NotFoundError requires an {error} body, but the moves controller's
inaccessible-recording 404 is a bare head :not_found with no payload.
New zero-member BareNotFoundError error shape — the generated OpenAPI
404 response now carries no content schema, so strict consumers won't
try to decode a body the server never sends. Runtime error-class
mapping is status-driven and unchanged (the per-SDK bare-404 tests
already pin NotFound classes against an empty body).
Copilot AI review requested due to automatic review settings July 31, 2026 16:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@jeremy
jeremy merged commit dc1b3c5 into main Jul 31, 2026
44 of 45 checks passed
@jeremy
jeremy deleted the c4-priorities branch July 31, 2026 16:16
@jeremy jeremy added the enhancement New feature or request label Jul 31, 2026
jeremy added a commit that referenced this pull request Aug 3, 2026
…ge (#593)

* Repin bc3 to 2c0dafba13 and absorb the Folders API it brings into range

The repin and the absorption are one change on purpose. `doc/api/sections/
folders.md` does not exist at the old pin `d0edc1283b`, so the five `/stacks`
routes are absent from `spec/bc3-routes.json` today. Repinning alone would make
them appear in the route table with no operation modelling them, and the #586
parity gate would then demand a disposition for five routes we have every
reason to model. Absorbing alone would model a contract the pin says isn't
there. 238 -> 243 operations.

## The repin: d0edc1283b -> 2c0dafba13 (11 commits)

Exactly one touches API surface:

- `dc6cd10714` #12384, the Folders API — the absorption half, below.

Two more are behaviour fixes to endpoints already modelled, with the wire shape
unchanged, so they need the repin and nothing else: `344581a379` (#12494) and
`2c0dafba13` (#12501) stop a draft's subscribers being recomputed when an update
doesn't address them. They add prose to five `doc/api` sections but no route and
no payload field — proven by regenerating the route table, whose only delta is
the five `/stacks` routes.

`19956c5579` (#12488) deserves its own sentence because it *deletes* a
`request.format.json?` branch, which reads like an API change. It is not. The
removed branch returned `nil` for JSON when the recording's own assignment
wasn't prioritized; the replacement returns that same assignment unconditionally
and `&.unprioritize` is a no-op when there is no priority. Both spellings are
exact-target on the id in the URL and both answer `204`. What actually changed
is that the *web* path stopped cascading to a sibling step — it adopted the
contract the JSON API has had since bc3#12483 (absorbed in SDK #528).

The remaining seven are four dev-tooling (`112c1664bf`, `63a34abe30`,
`5f889d8365`, `faf5fe9dfc`), two Turbo-morph web-only (`1fad1ef608`,
`c664cdc9de`), and one push-notification backend swap (`845df62b6b`).

`spec/bc3-routes.json` is regenerated at the new pin (369 routes, 64 sections);
`make provenance-sync` keeps the go:embed copy in step. The API version tracks
the pin date, so every SDK's `API_VERSION` moves to 2026-08-02.

## The absorption: FoldersService, five operations

Folders group projects on one person's home screen. They are per-user, so the
collection is flat rather than bucket-scoped. **The wire type stays `Stack`** —
the product was renamed, the payload was not — and so does the URI segment.
The operations, structures and generated methods use `Folder`.

    ListFolders    GET    /{accountId}/stacks.json        200, bare array
    GetFolder      GET    /{accountId}/stacks/{folderId}  200
    CreateFolder   POST   /{accountId}/stacks.json        201
    UpdateFolder   PUT    /{accountId}/stacks/{folderId}  200
    DeleteFolder   DELETE /{accountId}/stacks/{folderId}  204

Two output structures, not one. `Folder` is what the list returns; only
`FolderWithProjects` carries the expanded `projects`. A single shape with an
optional member would make every generated list-item type declare a field the
list response never populates.

`gauges_url`, `color` and `image_url` are **required-and-nullable** (SPEC §"A
third wire state"): the jbuilder emits all three unconditionally and all three
are commonly `null`, so `@required` models the presence and `smithy-build.json`
`jsonAdd` layers `type: ["string","null"]` on top. Go types them `*string`
because the value is nullable, not because the key is optional. Same treatment
`Wormhole.color`/`destination_url` already get.

`project_ids` does not round-trip: the create input takes it, and the response
reports the same ids as `bucket_ids` *and* expands them as `projects`. Three
names, one relationship. An unreachable id fails the whole request with 404 and
writes nothing, so `CreateFolder` lists `NotFoundError`; `UpdateFolder` lists
`ValidationError` because a blank name is a real 422 (create defaults it to
"New folder" instead).

`UpdateFolder` and `DeleteFolder` are naturally idempotent; `CreateFolder` is
not, and gets the inert retry block every non-idempotent POST carries.

The `Folders` tag resolves to `FoldersService` through every generator's default
fallback — zero service-group overrides, and `ListFolders` infers the `folder`
resource type cleanly, so no override there either. One deliberate generator
addition: `GetFolder` -> `getFolder` in TS/Kotlin/Swift, because the bare rule
collapses it to `folder(id)`, a noun with no verb, while Ruby and Python already
emit `get_folder`. Same shape as the `GetBookmark` override; all six SDKs now
agree on listFolders/getFolder/createFolder/updateFolder/deleteFolder.

Go's `FolderWithProjects` repeats the base fields rather than embedding `Folder`.
Embedding promotes them correctly at runtime but hides them from
`check-wrapper-drift`, which walks declared fields and reported all twelve as
missing. Repeating them keeps the guard able to verify every field.

## Registry and coordination

`spec/api-gaps/folders-api.md` moves to `absorbed-in-sdk` with seven
`smithy_refs`. `spec/api-gaps/stack-doc-and-smithy.md` deliberately keeps its
`confirmed-not-api-resource` status: it records a decision that was correct when
made, and the supersession note in `folders-api.md` exists precisely to preserve
that history. `COORDINATION.md` named pin `338b7a11`, three repins stale; it now
names `2c0dafba13` and says the provenance file is the only authority.

## Verified

`make check` exit 0. Swift is not skipped on this host: `swift build`/`swift
test` ran 306 tests with 0 failures, and `swift run ConformanceRunner` ran the
conformance suite. Ruby 1092 runs / 0 failures. Conformance 147 passed, 1
skipped (a pre-existing Go-runner origin case), and the five new `paths.json`
cases PASS in **all six** runners — Go, Kotlin, TypeScript, Ruby, Python and
Swift each have a dispatch branch, so none of them silently skips.
`BC3_REPO_PATH=... make bc3-routes-check` exit 0. Counts updated deliberately:
`check-idempotency-parity` 77/199 -> 79/203, SPEC/SECURITY/AGENTS 238 -> 243
operations, 122 -> 124 GETs, 47 -> 48 PUTs, 23 -> 24 DELETEs, 39 -> 40
single-attempt POSTs, 188 -> 192 retry-eligible ops at max 3.

* Folder writes declare FieldValidationError — the 422 they actually render

CreateFolder and UpdateFolder declared `ValidationError`, whose required wire
member is a singular `error` string. bc3 renders neither create nor update that
way. `stacks_controller.rb:51` answers a rejected rename with
`render json: { errors: @stack.errors }`, and `:27` answers a rejected create
with the same rendering from its `RecordInvalid` rescue. `ActiveModel::Errors`
serializes field-keyed, so the wire body is:

    {"errors":{"name":["can't be blank"]}}

`FieldValidationError` already exists for exactly this shape and is already
carried by `UpdateCalendar`, `UpdateMyNote` and `UpdateMyPreferences`. Both
folder writes now use it. No other operation is touched.

The failure this fixes is silent, which is why it earns a test. While the
operations declared `ValidationError`, Go's typed `JSON422` was a
`*ValidationErrorResponseContent`; decoding the field-keyed body into it
succeeds — `encoding/json` does not care that `error` is absent — and leaves
`JSON422.Error` as `""`. A caller reading the typed field got an empty string
and no error of any kind.

`TestFoldersGenerated_JSON422_DecodesFieldKeyedBody` goes at the generated
client directly and pins `JSON422.Errors` for both operations. It was shown to
fail before the fix: against the previous commit's `client.gen.go` it does not
compile, because `*ValidationErrorResponseContent` has no `Errors` field.

The two wrapper-level tests beside it (`TestFoldersService_{Update,Create}_
FieldKeyed422`) are deliberately not the proof. They were run against the
un-fixed generated client and **passed**, because `checkResponse` parses the raw
body itself and never reads the typed field. A wrapper-only assertion here would
have been vacuous.

The hand-written TS/Python/Ruby tests already modelled the field-keyed body, so
they needed no change — they were right about the server and the spec was the
wrong half.

make check: REAL_EXIT=0. Swift ran rather than skipped (306 tests, 0 failures;
`swift run ConformanceRunner` executed). Ruby 1092 runs, 0 failures. Conformance
147 passed / 1 skipped (pre-existing).

* SPEC §2: the per-op retry.max distribution counts the five Folders ops

Copilot caught this on #593. All five Folders operations carry `max: 3` in
behavior-model.json, so the `max:3` bucket is 200, not 195 — and 200 + 43 = 243
matches the new operation total. The neighbouring derived figures in the same
section (192 retry-eligible at max 3, 203 retry-eligible total) were updated
with the absorption, so leaving 195 made the section internally inconsistent.
Re-derived from behavior-model.json rather than incremented by hand. This prose
carries no CI gate.

* Swift conformance: read project_ids through the throwing intArray helper

Copilot flagged this as a suppressed low-confidence comment on #593, and it is
right. The Folders dispatch hand-rolled project_ids parsing with compactMap,
which silently drops any element that is not an integer or that overflows Int —
the exact "one step quieter" fault Dispatch.swift's own helper docstring
(lines 19-24) exists to warn about. A quietly truncated array would still have
satisfied the requestBody assertion.

intArray (lines 77-90) already returns [Int]? and throws badParameter on a
malformed element, and every other array read in this file goes through it.
Mine was the only exception.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conformance Conformance test suite documentation Improvements or additions to documentation enhancement New feature or request go kotlin python Pull requests that update the Python SDK ruby Pull requests that update the Ruby SDK spec Changes to the Smithy spec or OpenAPI swift typescript Pull requests that update TypeScript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants