Every route in the six Shift service controllers was diffed against the registered actions. shiftAtlas (35) and shiftObjects (8) already covered their surface. The other four did not — 84 actions became 91, and one whole domain, the issue lifecycle, went from unreachable to bound.
Added
| plugin | actions | what landed |
|---|---|---|
shiftWork |
23 → 29 | issue.report (the deduplicating /report route agents are meant to file through), issue.resolve / reopen / close, and the event timeline as issue.event.list / listPage |
shiftPages |
10 → 11 | page.vexArtifacts, plus type / deploymentId / artifactId / originUrl on create and update |
shiftOcr |
2 | provider and model overrides on ocr.extract |
shiftTranscription |
6 | job metadata on transcription.transcribe |
Vex artifacts can now be published as hosted pages. SHFT-881 shipped the cloud side in v0.22.0 and no plugin could reach it. protected_origin pages were selectable but uncreatable without originUrl — fixed here too. Page content rules are checked before the round-trip, so callers get the reason instead of a bare 400.
Fixed
The plugin build clears dist before compiling. tsc --noCheck never cleaned, so dist/plugins/shiftLabs survived the v0.22.0 split and a deleted plugin kept loading.
Breaking
Path parameters are named id everywhere. Callers migrate mechanically:
shiftPages.page.shares/share/renderUrl pageId -> id
shiftTranscription.transcription.transcript jobId -> id
shiftTranscription.transcription.artifact.list jobId -> id
shiftWork.issue.dependency.remove issueId -> id
Response bodies are unchanged — ShiftPageShare.pageId and ShiftIssueEvent.issueId still mirror the API's own payloads.
Notes
421 tests pass, up from 412. The v1 exclusion test for lifecycle transitions is replaced by coverage of the transitions themselves; it recorded scope, not a constraint, and carried no rationale.
Full Changelog: v0.22.0...v0.23.0