Background
The current PR #438 ships a one-line honesty fix for the core-WOPI SupportsCoauth boolean in 6a33dea: when ICobaltProcessor is registered, the host advertises SupportsCoauth = true because the MS-FSSHTTP / Cobalt protocol genuinely delivers multi-user editing. That covers the core CSPP CheckFileInfo property (docs) — informational only, no specific REST endpoint required.
This issue tracks the CSPP+ "Plus" tier coauth REST extension, which is a separate Microsoft-specific feature living at /microsoft-365/cloud-storage-partner-program/plus/coauthoring/*. The first endpoint is RefreshCoauthLock, which together with GetCoauthTable and an explicit coauth-lock creation operation forms the full Plus surface.
Scope
New endpoints (X-WOPI-Override dispatched on POST /wopi/files/{id}):
REFRESH_COAUTH_LOCK — refresh existing coauth lock by (file_id, CoauthLockId). Headers: X-WOPI-CoauthLockId (1024 ASCII chars), X-WOPI-CoauthLockExpirationTimeout (1-3600s), X-WOPI-CoauthLockMetadata (optional, 4KB). Response: X-WOPI-CoauthTableVersion header + JSON CoauthTable.
GET_COAUTH_TABLE — return current CoauthTable for the file.
- A "join/add coauth lock" operation — the spec says `RefreshCoauthLock` only works if an unexpired lock already exists, so there must be a separate primary call to create one.
Status codes: 200 (success), 400 (bad headers), 401 (invalid token), 404 (file not found or no edit perm), 409 (lock doesn't exist), 500, 501.
Why the existing infrastructure isn't a drop-in fit
| Requirement |
IWopiLockProvider |
CoauthoringSessionTracker |
| Many locks per file |
❌ single-lock-per-file |
✅ keyed by userId |
| 1024-char lock id (per client, not per user) |
✅ string id |
❌ keys by userId |
| Variable expiry 1–60 min (client-specified) |
❌ fixed 30 min |
❌ fixed 30 min |
| 4KB metadata blob per lock |
❌ no field |
❌ no field |
| Lock type (`Coauth` vs `CoauthExclusive`) |
❌ |
❌ |
| Versioned `CoauthTable` |
❌ |
❌ |
| Survives multi-instance deployment |
✅ via Azure/Redis providers |
❌ static state, single-process |
Specifically:
- `IWopiLockProvider` is one-lock-per-file by contract. Repurposing it for multi-entry-per-file would either change the storage key shape (`fileId+coauthLockId`) — which breaks the existing Memory/Azure/Redis providers — or conflate coauth locks with regular WOPI locks, a correctness hazard.
- `CoauthoringSessionTracker` keys by `userId`, not by client. The CSPP+ spec wants one lock per client (one user with two tabs = two coauth locks). And it's single-process today because Cobalt sessions are sticky to a WopiHost instance; CSPP+ REST is host-wide and needs distributed storage.
Proposed approach
Mirror the existing `IWopiLockProvider` package structure — don't reuse the types themselves:
```
WopiHost.Abstractions/
IWopiCoauthLockProvider ← new interface
CoauthLockInfo(LockId, UserId, Expires, ← new record
Metadata?, LockType)
WopiHost.MemoryCoauthLockProvider/ ← new (~120 LoC, parallels MemoryLockProvider)
WopiHost.AzureCoauthLockProvider/ ← new (~250 LoC, parallels AzureLockProvider)
WopiHost.RedisCoauthLockProvider/ ← new (~200 LoC, parallels RedisLockProvider;
Lua script for atomic table-version bump)
WopiHost.Abstractions.Testing/
CoauthLockProviderConformanceTests ← new conformance suite,
parallel to LockProviderConformanceTests
```
Then wire `CoauthoringSessionTracker` to read from the same store so Cobalt-side coauth status (`IsAlone`, `GetActiveEditorCount`, `GetEditorsTable`) reflects CSPP+ REST locks too. One logical "who's editing this file right now" state, two surface APIs (Cobalt protocol + CSPP+ REST).
Estimated scope
| Day |
Work |
| 1 |
`IWopiCoauthLockProvider` abstraction + `MemoryCoauthLockProvider` + conformance tests; `RefreshCoauthLock` endpoint with full state-machine tests |
| 2 |
`GetCoauthTable` endpoint + initial coauth-lock-add operation; `CoauthTable` versioning state machine; wire `CoauthoringSessionTracker` to read the new store |
| 3 |
`AzureCoauthLockProvider` + `RedisCoauthLockProvider` + their conformance subclasses + integration tests |
After landing, `SupportsCoauth = cobaltProcessor is not null && coauthLockProvider is not null` would be honest in both the legacy ("we support multi-user via Cobalt") and CSPP+ ("we implement the REST coauth surface") senses.
Acceptance
References
Background
The current PR #438 ships a one-line honesty fix for the core-WOPI
SupportsCoauthboolean in6a33dea: whenICobaltProcessoris registered, the host advertisesSupportsCoauth = truebecause the MS-FSSHTTP / Cobalt protocol genuinely delivers multi-user editing. That covers the core CSPP CheckFileInfo property (docs) — informational only, no specific REST endpoint required.This issue tracks the CSPP+ "Plus" tier coauth REST extension, which is a separate Microsoft-specific feature living at
/microsoft-365/cloud-storage-partner-program/plus/coauthoring/*. The first endpoint isRefreshCoauthLock, which together withGetCoauthTableand an explicit coauth-lock creation operation forms the full Plus surface.Scope
New endpoints (X-WOPI-Override dispatched on
POST /wopi/files/{id}):REFRESH_COAUTH_LOCK— refresh existing coauth lock by(file_id, CoauthLockId). Headers:X-WOPI-CoauthLockId(1024 ASCII chars),X-WOPI-CoauthLockExpirationTimeout(1-3600s),X-WOPI-CoauthLockMetadata(optional, 4KB). Response:X-WOPI-CoauthTableVersionheader + JSONCoauthTable.GET_COAUTH_TABLE— return currentCoauthTablefor the file.Status codes: 200 (success), 400 (bad headers), 401 (invalid token), 404 (file not found or no edit perm), 409 (lock doesn't exist), 500, 501.
Why the existing infrastructure isn't a drop-in fit
IWopiLockProviderCoauthoringSessionTrackerSpecifically:
Proposed approach
Mirror the existing `IWopiLockProvider` package structure — don't reuse the types themselves:
```
WopiHost.Abstractions/
IWopiCoauthLockProvider ← new interface
CoauthLockInfo(LockId, UserId, Expires, ← new record
Metadata?, LockType)
WopiHost.MemoryCoauthLockProvider/ ← new (~120 LoC, parallels MemoryLockProvider)
WopiHost.AzureCoauthLockProvider/ ← new (~250 LoC, parallels AzureLockProvider)
WopiHost.RedisCoauthLockProvider/ ← new (~200 LoC, parallels RedisLockProvider;
Lua script for atomic table-version bump)
WopiHost.Abstractions.Testing/
CoauthLockProviderConformanceTests ← new conformance suite,
parallel to LockProviderConformanceTests
```
Then wire `CoauthoringSessionTracker` to read from the same store so Cobalt-side coauth status (`IsAlone`, `GetActiveEditorCount`, `GetEditorsTable`) reflects CSPP+ REST locks too. One logical "who's editing this file right now" state, two surface APIs (Cobalt protocol + CSPP+ REST).
Estimated scope
After landing, `SupportsCoauth = cobaltProcessor is not null && coauthLockProvider is not null` would be honest in both the legacy ("we support multi-user via Cobalt") and CSPP+ ("we implement the REST coauth surface") senses.
Acceptance
References
6a33dea— the core-CSPPSupportsCoauthhonesty fix that prompted this issue