Skip to content

v0.0.4

Choose a tag to compare

@khj809 khj809 released this 18 Sep 08:47
· 105 commits to main since this release
f1e5055

🤖 The agent loop

We're applying an agent-based automation loop to Backlot's own maintenance: a Claude Code routine takes an issue labelled agent, measures the real vendor API, fixes the divergence and opens a pull request that two reviewer agents have passed, leaving the merge to a person. It is still at an early stage, and we're looking forward to stabilising it in the near future. How to drive it is in docs/loop.md.

⚠️ Breaking changes

  • A corpus imported before this release has to be re-imported. Drive files gained a folded title and Slack a table for deactivated members; the server names both at startup. A record needs no change unless it is one below. (#201, #259)
  • A Slack reaction or edit names its people by address. reactions[].users is a non-empty list of addresses with no count, and edited is {"user": <the author's address>, "ts": <a later second>}; the router mints the Slack ids. A corpus carrying "users": ["U01"] stops at import. (#164, #257)
  • Notion requires Notion-Version. A missing header or an unpublished version is refused with real's message; the seven published versions pass. 2025-09-03 is no longer assumed for a caller that sent nothing. (#226)

🔧 Fixes

Each closes a measured divergence from the vendor's real API. Those that change what a source answers are marked in bold.

Amazon S3

  • A bare bucket GET is ListObjects and list-type=2 is ListObjectsV2, each reading its own parameters and refusing the other's. Backlot answered the V2 shape regardless. A delimited V1 walk now terminates. (#204)
  • encoding-type=url is applied, which boto3 sends on every listing; a repeated parameter reads its first value; max-keys=-1 is real's 400 rather than a bare 500. (#204)
  • An undecodable continuation-token, or an empty one, is refused instead of answered with page one. (#281)
  • A HEAD on a bucket sub-resource carries Allow: GET where the same path serves a GET. (#241)

Notion

  • Notion-Version selects the database query path: databases/{id}/query before 2025-09-03, data_sources/{id}/query and GET data_sources/{id} from it, each invalid_request_url outside its range. Every route declares the header, so backlot mcp sends it. (#226)
  • The AI-plugins and AI-skills directory is an acknowledged gap. (#252)

Google

  • Drive's files.list parses q as the reference's grammar — and, or, not, parentheses, every operator per field — and refuses a clause it cannot evaluate rather than dropping it and answering the whole listing. Six of the issue's seven queries answered every file before. (#201)
  • A Sheets values read accepts R1C1, which LlamaIndex's GoogleSheetsReader sends; point_sheets_at redirects that reader, with an example. (#201)
  • Every Google error body is rendered the way real writes one: indented, charset=UTF-8, real's 209 escaped characters. callback= on a GET answers the error as JSONP at 200; alt is read case-insensitively. (#220)
  • $.xgafv is honoured on every route: 1 adds the legacy errors[] array where each family's own rule says so, and any other value is refused ahead of everything else. (#202)
  • backlot mcp --source gdrive offers Docs, Sheets and Slides beside Drive — thirteen tools where there were six. (#199)
  • backlot diff compares Google's batch endpoint against the batchPath its documents declare. (#283)

Atlassian

  • A query parameter is read the way each product's own binder reads it: an empty value is the default, whitespace is removed, a repeated integer takes its first value, startAt is a long, Confluence refuses a negative where Jira clamps. ?limit=-1 no longer reaches SQLite as "no limit". A conversion failure carries Jira's RFC 7807 body or Confluence's Spring pair. (#206)
  • Jira's search/jql reads the query string on GET and the body on POST, nothing else. A POST with no body, a wrong media type, an undecodable nextPageToken or a missing jql is refused the way real refuses it. (#213)
  • Confluence's space listing answers a page: limit and start are read and _links carries next/prev spelled as real spells them, cut from the caller's own reachable spaces. (#271)
  • The space permission roster is ?expand=permissions, one entry per grant, and GET space/{key}/permission is real's 405. A wrong method carries each product's own body, Jira's with Allow. (#245)
  • A Jira JSON body is application/json;charset=UTF-8; Confluence stays bare. (#284)
  • The Forge panel pin status and workflow copy are acknowledged gaps. (#228)

Slack

  • A deactivated member is deleted: true, absent from conversations.members, and account_inactive on their own token, while their messages stay. A roster entry states deactivated: true. (#259)
  • A reaction's users and an edit's user are rendered from addresses, so no invented Slack id is left in the bundled corpora. (#164, #257)

GitHub

  • A trailing slash is a 404, not a 307, real having no slash redirect; /contents/ stays the root listing's 200. The 404 wins over a bad bearer. (#250)
  • A bad bearer is answered ahead of an unsupported X-GitHub-Api-Version; a caller with no credential still meets the version check first. (#243)
  • The actions-policies family is an acknowledged gap. (#253)

Linear

  • IntegrationService carries datadog; IssueLabel.groupType is served, which @linear/sdk 95.1 selects; four other new fields are acknowledged gaps. (#227, #275)

Tooling, packaging and CI

  • scripts/gen_docs.py renders from the checkout it runs in, not whichever backlot is installed. (#282)
  • google-auth>=2.55 in the official-sdk extra; the linear example tracks @linear/sdk 95.1. (#211, #209, #275)
  • ruff 0.16.7, mcp 2.2.0, the actions group. (#210, #211)

Full changelog: v0.0.3...v0.0.4