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
136 changes: 136 additions & 0 deletions behavior-model.json
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,22 @@
]
}
},
"GetBubbleUps": {
"readonly": true,
"pagination": {
"style": "link",
"maxPageSize": 50
},
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetCampfire": {
"readonly": true,
"retry": {
Expand Down Expand Up @@ -742,6 +758,126 @@
]
}
},
"GetEverythingBoosts": {
"readonly": true,
"pagination": {
"style": "link",
"maxPageSize": 50
},
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetEverythingCheckins": {
"readonly": true,
"pagination": {
"style": "link",
"maxPageSize": 50
},
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetEverythingComments": {
"readonly": true,
"pagination": {
"style": "link",
"maxPageSize": 50
},
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetEverythingFiles": {
"readonly": true,
"pagination": {
"style": "link",
"maxPageSize": 50
},
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetEverythingForwards": {
"readonly": true,
"pagination": {
"style": "link",
"maxPageSize": 50
},
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetEverythingMessages": {
"readonly": true,
"pagination": {
"style": "link",
"maxPageSize": 50
},
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetEverythingOverdueCards": {
"readonly": true,
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetEverythingOverdueTodos": {
"readonly": true,
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
503
]
}
},
"GetForward": {
"readonly": true,
"retry": {
Expand Down
11 changes: 9 additions & 2 deletions conformance/runner/typescript/schema-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,18 @@ describe("validateResponse — ListProjects (array root)", () => {
// =============================================================================

describe("validateResponse — GetMyNotifications (object root, array fields)", () => {
it("validates an empty payload (all arrays absent)", () => {
const result = validateResponse("GetMyNotifications", {});
// bubble_ups_count / scheduled_bubble_ups_count are @required on the output,
// so every valid payload carries them (the arrays remain optional).
const counts = { bubble_ups_count: 0, scheduled_bubble_ups_count: 0 };

it("validates a minimal payload (arrays absent, required counts present)", () => {
const result = validateResponse("GetMyNotifications", { ...counts });
expect(result.ok).toBe(true);
});

it("validates with empty arrays", () => {
const result = validateResponse("GetMyNotifications", {
...counts,
unreads: [],
reads: [],
memories: [],
Expand All @@ -87,6 +92,7 @@ describe("validateResponse — GetMyNotifications (object root, array fields)",

it("collects extras at the root", () => {
const result = validateResponse("GetMyNotifications", {
...counts,
unreads: [],
hypothetical_new_top_level: 42,
});
Expand All @@ -101,6 +107,7 @@ describe("validateResponse — GetMyNotifications (object root, array fields)",
updated_at: "2026-01-02T00:00:00Z",
};
const result = validateResponse("GetMyNotifications", {
...counts,
unreads: [{ ...minimalNotification, future_envelope_field: "BC5 addition" }],
});
expect(result.ok).toBe(true);
Expand Down
40 changes: 40 additions & 0 deletions conformance/tests/live-my-surface.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
[
{
"mode": "live",
"name": "GetProgressReport account feed validates against the timeline schema",
"description": "Exercises the account activity feed and validates each timeline event against the schema, including the additive kind/avatars_sample/data/attachments fields. Live-dormant: validates statically until credentials are provisioned.",
"operation": "GetProgressReport",
"method": "GET",
"path": "/reports/progress.json",
"tags": ["live", "read-only"],
"liveAssertions": [
{ "type": "liveCallSucceeds" },
{ "type": "liveSchemaValidate" }
]
},
{
"mode": "live",
"name": "GetBubbleUps returns a paginated bubble-ups list and validates against schema",
"description": "Exercises the dedicated bubble-ups endpoint (current + scheduled, 50/page) and validates each notification item against the schema. Live-dormant: validates statically until credentials are provisioned.",
"operation": "GetBubbleUps",
"method": "GET",
"path": "/my/readings/bubble_ups.json",
"tags": ["live", "read-only"],
"liveAssertions": [
{ "type": "liveCallSucceeds" },
{ "type": "liveSchemaValidate" }
]
},
{
"mode": "live",
"name": "ListProjects returns a list and validates against schema",
Expand Down Expand Up @@ -156,5 +182,19 @@
{ "type": "liveCallSucceeds" },
{ "type": "liveSchemaValidate" }
]
},
{
"mode": "live",
"name": "ListRecordings type=Door decodes the external-link (door) shape",
"description": "DECODING coverage for the absorbed external-links/doors list surface (spec/api-gaps/external-links-doors.md, bc3 #12375). Drives the type=Door recordings query and validates each recording against the Recording schema, including the door-specific url/service/description/position fields. Live-dormant: validates statically until credentials are provisioned. Create/image/composite remain residual gaps (see the entry).",
"operation": "ListRecordings",
"method": "GET",
"path": "/projects/recordings.json",
"queryParams": { "type": "Door" },
"tags": ["live", "read-only", "bc5-additive"],
"liveAssertions": [
{ "type": "liveCallSucceeds" },
{ "type": "liveSchemaValidate" }
]
}
]
2 changes: 1 addition & 1 deletion go/pkg/basecamp/api-provenance.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"bc3": {
"branch": "master",
"revision": "ca1d34bcc40d2e2403a08bce9ead0d4b0276b1fa",
"revision": "d7bc88dafad61d15ad65a579f7a457037eca2b32",
"date": "2026-07-24"
},
"compatibility": {
Expand Down
11 changes: 11 additions & 0 deletions go/pkg/basecamp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type AccountClient struct {
gauges *GaugesService
myAssignments *MyAssignmentsService
myNotifications *MyNotificationsService
everything *EverythingService
}

// Response wraps an API response.
Expand Down Expand Up @@ -1325,6 +1326,16 @@ func (ac *AccountClient) Timeline() *TimelineService {
return ac.timeline
}

// Everything returns the EverythingService for account-wide aggregate listings.
func (ac *AccountClient) Everything() *EverythingService {
ac.mu.Lock()
defer ac.mu.Unlock()
if ac.everything == nil {
ac.everything = NewEverythingService(ac)
}
return ac.everything
}

// Reports returns the ReportsService for reports operations.
func (ac *AccountClient) Reports() *ReportsService {
ac.mu.Lock()
Expand Down
Loading
Loading