Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ typescript/coverage/

# Kotlin SDK build artifacts
kotlin/.gradle/
kotlin/.kotlin/
kotlin/build/
kotlin/sdk/build/
kotlin/generator/build/
Expand Down
49 changes: 46 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,14 @@ All SDKs are generated from a single Smithy specification. When adding support f
- Define the resource, operations, and shapes
- Follow patterns from existing resources (e.g., `Project`, `Todo`)

2. **Regenerate the OpenAPI spec**
2. **Regenerate everything** in one step:
```bash
make smithy-build
make generate
```

3. **Run per-SDK generators** to update generated service code:
This runs Smithy build, behavior model, URL routes, provenance sync, and per-language generators (TypeScript, Ruby, Python, Kotlin, Swift, Go) in dependency order.

3. **Run per-SDK generators individually** if you only need one:
- **Go:** `make go-check-drift` — Go services are hand-written wrappers around the generated client; the drift check verifies all generated operations are covered
- **TypeScript:** `make ts-generate-services`
- **Ruby:** `make rb-generate-services`
Expand All @@ -229,6 +231,47 @@ All SDKs are generated from a single Smithy specification. When adding support f
- Add to the services table in each SDK's README
- Add to CHANGELOG under `[Unreleased]`

## Spec-shape lints

The repo enforces a small set of structural invariants on the OpenAPI spec
beyond the language-specific drift checks. These run as part of `make check`:

- **Bucket↔flat parity** (`make check-bucket-flat-parity`): every
`GET /{accountId}/buckets/{bucketId}/<resource>(/...).json` list operation
must have a flat counterpart at `/{accountId}/<resource>.json`, or be
justified in [`spec/bucket-scoped-allowlist.txt`](spec/bucket-scoped-allowlist.txt).
The intent is that cross-project SDK consumers shouldn't have to walk every
project to query account-wide resources.

When adding a bucket-scoped list endpoint, either add the matching flat
endpoint or append a one-line entry to the allowlist with a justification
comment.

## API gap registry (`spec/api-gaps/`)

When BC ships a new user-visible feature without a JSON API (or with an
incomplete one), add an entry under [`spec/api-gaps/`](spec/api-gaps/).
The registry is the SDK side of the [BC3 API parity coordination](COORDINATION.md):
the BC3 plan owns server-side delivery; the registry tracks the gap from
detection through absorption, with status changes in git history.

To add a new entry:

1. Copy an existing entry in `spec/api-gaps/` as a template.
2. Set frontmatter status to `no-json-contract` (or `partial-coverage` /
`ambiguous` as appropriate). See
[`spec/api-gaps/schema.json`](spec/api-gaps/schema.json) for valid
statuses.
3. Add a row to the table in
[`spec/api-gaps/README.md`](spec/api-gaps/README.md).
4. Run `make validate-api-gaps` to confirm frontmatter and required body
sections are well-formed. Wired into `make check`.

For routes that should *not* warrant an entry (transient nav state, internal
endpoints, duplicates of a route already covered elsewhere), add a record
to [`spec/api-gaps/allowlist.yml`](spec/api-gaps/allowlist.yml) with a
justification.

## Reporting Issues

- Use GitHub Issues for bug reports and feature requests
Expand Down
56 changes: 56 additions & 0 deletions COORDINATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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
`script/api/audit`).

## Division of labor

- **BC3 plan owns**: server-side audit, jbuilder + controller + doc
additions, BC4 compat verification, regenerating live-server doc
snapshots, eventually opening Smithy PRs in this repo.
- **This SDK plan owns**: live canary against both backends, schema +
pairwise validation on raw wire bytes, additive Smithy absorption per
BC3 deliverable, the API-gap detector that catches anything BC3 hasn't
registered, the bucket-flat parity lint, the API gap registry at
[`spec/api-gaps/`](spec/api-gaps/).

## Lifecycle per gap

1. BC3 plan inventories a gap (Phase 1 audit) → SDK adds a corresponding
entry in [`spec/api-gaps/`](spec/api-gaps/).
2. BC3 ships the JSON API for the gap → SDK canary auto-detects new
fields/endpoints.
3. SDK opens a Smithy + per-language regeneration PR absorbing the new
contract.
4. Entry frontmatter updates: `status: addressed-in-bc3-pr-N` then
`status: absorbed-in-sdk` with Smithy refs.

## 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.
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`.

Both are tracked in the BC3 plan's Phase 2 compat verification 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`)
- 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)
- Cross-team alignment status: this file

— The basecamp-sdk team
61 changes: 46 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,17 @@ BC3_REPO ?= basecamp/bc3
sync-status:
@command -v gh > /dev/null 2>&1 || { echo "ERROR: gh CLI not found. Install: https://cli.github.com"; exit 1; }
@gh auth status > /dev/null 2>&1 || { echo "ERROR: gh not authenticated. Run: gh auth login"; exit 1; }
@REV=$$(jq -r '.bc3.revision // empty' spec/api-provenance.json); \
if [ -z "$$REV" ]; then \
echo "==> bc3 API docs: no baseline revision set"; \
@./scripts/report-bc3-drift.sh \
"$$(jq -r '.bc3.revision // empty' spec/api-provenance.json)" \
"$$(jq -r '.bc3.branch // "master"' spec/api-provenance.json)" \
"primary"
@COMPAT_REV=$$(jq -r '.compatibility["bc3-master"].revision // empty' spec/api-provenance.json); \
if [ -n "$$COMPAT_REV" ]; then \
echo ""; \
echo "==> bc3 API implementation: no baseline revision set"; \
else \
SHORT_REV=$$(echo $$REV | cut -c1-7); \
echo "==> bc3 API docs changes since last sync ($$SHORT_REV):"; \
gh api "repos/$(BC3_REPO)/compare/$$REV...HEAD" \
--jq '[.files[] | select(.filename | startswith("doc/api/"))] | if length == 0 then " (no changes in doc/api/)" else .[] | " " + .status[:1] + " " + .filename end'; \
echo ""; \
echo "==> bc3 API implementation changes since last sync ($$SHORT_REV):"; \
gh api "repos/$(BC3_REPO)/compare/$$REV...HEAD" \
--jq '[.files[] | select(.filename | startswith("app/controllers/"))] | if length == 0 then " (no changes in app/controllers/)" else .[] | " " + .status[:1] + " " + .filename end'; \
./scripts/report-bc3-drift.sh \
"$$COMPAT_REV" \
"$$(jq -r '.compatibility["bc3-master"].branch // "master"' spec/api-provenance.json)" \
"compat"; \
fi

#------------------------------------------------------------------------------
Expand Down Expand Up @@ -572,12 +569,45 @@ tools:
@command -v swift >/dev/null 2>&1 || echo "NOTE: swift is optional (macOS: xcode-select --install, Arch: yay -S swift-bin)"
@echo "==> Done"

#------------------------------------------------------------------------------
# Spec-shape lints
#------------------------------------------------------------------------------

.PHONY: check-bucket-flat-parity validate-api-gaps

# Verify every bucket-scoped GET list operation has a flat-path counterpart
# (or is justified in spec/bucket-scoped-allowlist.txt). Cross-project SDK
# consumers shouldn't need to enumerate projects to reach account-wide data.
check-bucket-flat-parity:
@./scripts/check-bucket-flat-parity.sh

# Validate spec/api-gaps/ entry frontmatter, required body sections, and allowlist.
validate-api-gaps:
@./scripts/validate-api-gaps.sh

#------------------------------------------------------------------------------
# Combined targets
#------------------------------------------------------------------------------

.PHONY: generate

# Regenerate every machine-derived artifact in the repo, in dependency order.
# Run after editing spec/basecamp.smithy or spec/api-provenance.json.
# Sequential phases via sub-makes so language generators don't run in
# parallel against a stale openapi.json under `make -j`.
generate:
@$(MAKE) smithy-build
@$(MAKE) behavior-model url-routes provenance-sync
@$(MAKE) ts-generate ts-generate-services \
rb-generate rb-generate-services \
py-generate \
kt-generate-services \
swift-generate
@$(MAKE) -C go generate
@echo "==> Generation complete"

# Run all checks (Smithy + Go + TypeScript + Ruby + Kotlin + Swift + Python + Behavior Model + Conformance + Provenance + Actions lint)
check: lint-actions sync-spec-version-check smithy-check behavior-model-check provenance-check sync-api-version-check go-check-drift auth-routable-check kt-check-drift go-check ts-check rb-check kt-check swift-check py-check conformance
check: lint-actions sync-spec-version-check smithy-check behavior-model-check provenance-check sync-api-version-check go-check-drift auth-routable-check kt-check-drift go-check ts-check rb-check kt-check swift-check py-check conformance check-bucket-flat-parity validate-api-gaps
Comment thread
jeremy marked this conversation as resolved.
@echo "==> All checks passed"

# Clean all build artifacts
Expand Down Expand Up @@ -682,6 +712,7 @@ help:
@echo " tools Install development tools (smithy, golangci-lint, actionlint, zizmor)"
@echo ""
@echo "Combined:"
@echo " check Run all checks (Smithy + behavior-model/drift + Go + TypeScript + Ruby + Swift + Kotlin + Python + Conformance + Provenance + API version sync + Actions lint)"
@echo " generate Regenerate every machine-derived artifact (Smithy + per-language SDKs + provenance)"
@echo " check Run all checks (Smithy + behavior-model/drift + Go + TypeScript + Ruby + Swift + Kotlin + Python + Conformance + Provenance + API version sync + parity lint + api-gaps + Actions lint)"
@echo " clean Remove all build artifacts"
@echo " help Show this help"
1 change: 1 addition & 0 deletions behavior-model.json
Original file line number Diff line number Diff line change
Expand Up @@ -2597,6 +2597,7 @@
"$.email_address",
"$.title",
"$.bio",
"$.tagline",
"$.location",
"$.avatar_url"
],
Expand Down
12 changes: 10 additions & 2 deletions go/pkg/basecamp/api-provenance.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
{
"bc3": {
"revision": "056a356ee0d3b018362adbc8b44703df0567adbf",
"date": "2026-03-23"
"branch": "five",
"revision": "a790dc09461d8729a7d4f6d875476581f59f3b40",
"date": "2026-05-01"
},
"compatibility": {
"bc3-master": {
"branch": "master",
"revision": "f2450b7a1ac6a52b672975021c7c327cecc51852",
"date": "2026-05-01"
}
}
}
2 changes: 1 addition & 1 deletion go/pkg/basecamp/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ package basecamp
const Version = "0.7.3"

// APIVersion is the Basecamp API version this SDK targets.
const APIVersion = "2026-03-23"
const APIVersion = "2026-05-01"
111 changes: 78 additions & 33 deletions go/pkg/generated/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading