Skip to content

Commit b355608

Browse files
committed
refactor(Collector.checkInbox): opportunistic legacy-event migration (Plan 58 Phase 4d)
The patient side (Phase 4c) no longer emits `apiEndpoint` on `update-accept` response events — token is preserved by `accesses.update`. But legacy events written by the old delete+create patient flow may still sit in inboxes waiting to be processed, and those carry a meaningful `apiEndpoint` from the token rotation. Doctor-side handling now distinguishes the two cases: - Legacy `update-accept` (responseEvent.content.apiEndpoint set): - Adopt the rotated apiEndpoint as before (legacy semantic preserved). - When archiving the response event, write content stripped of `apiEndpoint` (events.update REPLACES content, verified empirically on Plan 66 demo). - New `update-accept` (no apiEndpoint): - Stored apiEndpoint stays valid; archive without content rewrite. Idempotency: archive moves the event out of the inbox stream, so re-reads won't see it. Failed archive → next checkInbox retries same processing. 488/488 tests pass. Lint clean.
1 parent 3dc2eed commit b355608

1 file changed

Lines changed: 24 additions & 5 deletions

File tree

ts/appTemplates/Collector.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,14 @@ export class Collector {
213213
};
214214
updateInvite.content.sourceEventId = responseEvent.id;
215215

216+
// Plan 58 Phase 4d: legacy update-accept events (pre-Plan-58, delete+create era)
217+
// carry an apiEndpoint field that must be adopted (token rotation happened).
218+
// New update-accept events omit apiEndpoint (token preserved by accesses.update).
219+
// When archiving a legacy event, opportunistically strip apiEndpoint from its
220+
// content so the archive stays clean. events.update REPLACES content (not merges),
221+
// so the strip is just "write current content minus the field".
222+
let stripLegacyApiEndpointFromResponse = false;
223+
216224
// check type of response
217225
switch (responseEvent.content.type) {
218226
case 'accept':
@@ -222,9 +230,13 @@ export class Collector {
222230
if (responseEvent.content.system) updateInvite.content.system = responseEvent.content.system;
223231
break;
224232
case 'update-accept':
225-
// Patient accepted an access update — new apiEndpoint, stays active
226233
(updateInvite as any).streamIds = [this.streamIdFor(Collector.STREAMID_SUFFIXES.active)];
227-
updateInvite.content.apiEndpoint = responseEvent.content.apiEndpoint;
234+
// Legacy events: adopt the rotated apiEndpoint and flag the response for migration.
235+
// New events: omit the field; the stored apiEndpoint stays valid in place.
236+
if (responseEvent.content.apiEndpoint != null) {
237+
updateInvite.content.apiEndpoint = responseEvent.content.apiEndpoint;
238+
stripLegacyApiEndpointFromResponse = true;
239+
}
228240
if (responseEvent.content.chat) updateInvite.content.chat = responseEvent.content.chat;
229241
if (responseEvent.content.system) updateInvite.content.system = responseEvent.content.system;
230242
break;
@@ -244,6 +256,15 @@ export class Collector {
244256
throw new HDSLibError(`Unkown or undefined ${responseEvent.content.type}`, responseEvent);
245257
}
246258

259+
// Archive update — also strip legacy apiEndpoint when applicable.
260+
const responseArchiveUpdate: { streamIds: string[], content?: any } = {
261+
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.archive)]
262+
};
263+
if (stripLegacyApiEndpointFromResponse) {
264+
const { apiEndpoint: _stripped, ...cleanedContent } = responseEvent.content;
265+
responseArchiveUpdate.content = cleanedContent;
266+
}
267+
247268
// update inviteEvent and archive inbox message
248269
const apiCalls = [
249270
{
@@ -257,9 +278,7 @@ export class Collector {
257278
method: 'events.update',
258279
params: {
259280
id: responseEvent.id,
260-
update: {
261-
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.archive)]
262-
}
281+
update: responseArchiveUpdate
263282
}
264283
}
265284
];

0 commit comments

Comments
 (0)