@twinsearth/w2m-dsh-plugin 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,8 @@ import {
22
22
  RabbitState,
23
23
  rfc3339,
24
24
  } from './state.mjs';
25
- import { createFleetMessages } from './fleet-messages.mjs';
25
+ import { createFleetMessages } from './fleet-messages.mjs';
26
+ import { createFleetMemory } from './fleet-memory.mjs';
26
27
  import { createJoinChallenges } from './join-challenge.mjs';
27
28
  import { createAdmission } from './admission.mjs';
28
29
  import { Persistence } from './persistence.mjs';
@@ -1045,6 +1046,7 @@ export class RelayServer {
1045
1046
  // speak as another. The refusal codes from the store are passed through unchanged (FLEET_QUOTA_EXCEEDED included)
1046
1047
  // because a caller that cannot tell "too big" from "over quota" from "not text" cannot tell the operator what to do.
1047
1048
  this.fleetMessages ??= createFleetMessages({ now: () => this.state.nowMs() });
1049
+ this.fleetMemory ??= createFleetMemory({ now: () => this.state.nowMs() });
1048
1050
 
1049
1051
  if (method === 'POST' && path === '/v1/fleet/messages') {
1050
1052
  const body = takeJson();
@@ -1115,6 +1117,37 @@ export class RelayServer {
1115
1117
  });
1116
1118
  }
1117
1119
 
1120
+ // v1.2.2: shared memory writes. Compare-and-swap on a revision, because two machines appending to one document is
1121
+ // otherwise last-writer-wins over the whole thing -- a record one machine added would vanish because the other had an
1122
+ // older copy. A stale writer gets 409 and the records it has not read; it is NOT retried here, because retrying in
1123
+ // the transport turns a visible conflict into a silent overwrite.
1124
+ if (path.startsWith('/v1/fleet/memory/')) {
1125
+ const doc = decodeURIComponent(path.slice('/v1/fleet/memory/'.length));
1126
+ if (doc === '') throw new ProtocolError('BAD_REQUEST', 'memory doc is required', { method, path });
1127
+ this.fleetMemory ??= createFleetMemory({ now: () => this.state.nowMs() });
1128
+ if (method === 'GET') {
1129
+ return sendJson(res, 200, { protocol_version: PROTOCOL_VERSION, rabbit_time: this.state.nowIso(), ...this.fleetMemory.get(doc) });
1130
+ }
1131
+ if (method === 'PUT') {
1132
+ const body = takeJson();
1133
+ const appended = this.fleetMemory.append({
1134
+ doc,
1135
+ if_revision: Number.isInteger(body.if_revision) ? body.if_revision : null,
1136
+ author_machine_id: device.machine_id,
1137
+ records: Array.isArray(body.records) ? body.records : (typeof body.text === 'string' ? [body.text] : []),
1138
+ });
1139
+ if (appended.ok !== true) {
1140
+ const status = appended.code === 'REVISION_CONFLICT' ? 409 : 400;
1141
+ return sendJson(res, status, {
1142
+ protocol_version: PROTOCOL_VERSION, ok: false,
1143
+ error: { code: appended.code }, detail: appended.detail ?? null,
1144
+ });
1145
+ }
1146
+ return sendJson(res, 200, { protocol_version: PROTOCOL_VERSION, rabbit_time: this.state.nowIso(), ...appended });
1147
+ }
1148
+ throw new ProtocolError('BAD_REQUEST', 'memory accepts GET or PUT', { method, path });
1149
+ }
1150
+
1118
1151
  throw new ProtocolError('NOT_FOUND', `no route for ${method} ${path}`, { method, path });
1119
1152
  }
1120
1153