@ours.network/messenger-server 1.0.31-nightly.14 → 1.0.31-nightly.16

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.
package/README.md CHANGED
@@ -413,3 +413,25 @@ Set `OURS_NOTIFICATIONS_ORIGIN` and a Messenger-scoped `OURS_NOTIFICATIONS_PRODU
413
413
  Workspace enrollment supports optional `preserveProfile: true` on its protected POST endpoint. This skips human profile creation/replacement (including an absent profile); root checks and the signed binding command remain mandatory. The protected enrollment-identity response advertises `preserveProfile: true` so clients can fail closed against older servers. Omitted/false keeps fresh-host account profile setup.
414
414
 
415
415
  Messenger may run as the Human root or as a permanent identity under it. For the latter, the enrollment-identity response also carries `rootCid`, the root the daemon describes for the bound identity; `POST /api/identity/profile` accepts both. Only a Messenger bound to the root itself signs the workspace binding on `POST /api/workspace/enroll`; for an identity under the root the caller obtains that signature from the root outside Messenger.
416
+
417
+ After successful contact removal, the notification producer durably retires that
418
+ conversation through the notification service's scoped `/api/v1/delete-target`
419
+ route. Pending sends for that contact are removed; cleanup follows any already
420
+ inflight send and retries after service outages or restart. Watch replay confirms
421
+ absence against a complete contacts/pending inventory before dropping an event.
422
+ Inventory errors preserve the queue. The gateway and notification service must
423
+ support this producer lifecycle route before source cleanup can be delivered.
424
+
425
+ Profile-preserving workspace enrollment reuses an already authenticated contact
426
+ with the exact pinned enrollment server CID. This permits a fresh signed account
427
+ challenge after the one-time invitation was used for registration retirement;
428
+ account-side invitation provenance or an exact signed replacement handoff still
429
+ controls proof acceptance.
430
+
431
+ `POST /api/workspace/unregister` carries the first-party retirement command when
432
+ Messenger already holds the Human root lease. It requires local API/CSRF
433
+ authentication, a permanent root matching `rootCid`, the exact ready enrollment
434
+ server contact, and well-formed workspace/host/nonce identifiers. An optional
435
+ three-field replacement challenge is forwarded unchanged. The account service
436
+ owns retirement authorization and cleanup; this route only sends the signed
437
+ command and acknowledges submission.
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  var define_MESSENGER_BUILD_INFO_default;
39
39
  var init_define_MESSENGER_BUILD_INFO = __esm({
40
40
  "<define:__MESSENGER_BUILD_INFO__>"() {
41
- define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.14", sha: "472ad12fdb461e51398c29e8e224d12caca9a43e", dirty: true };
41
+ define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.16", sha: "64376e7737f42d8375850bfb548840977e7250d2", dirty: true };
42
42
  }
43
43
  });
44
44
 
@@ -10,7 +10,7 @@ import {
10
10
  publicEngineError,
11
11
  publicInternalError,
12
12
  reportFailure
13
- } from "./chunk-XNYNQSPB.js";
13
+ } from "./chunk-NB6YYTBL.js";
14
14
 
15
15
  // node_modules/bn.js/lib/bn.js
16
16
  var require_bn = __commonJS({
@@ -868,23 +868,23 @@ var require_bn = __commonJS({
868
868
  var lo = r & 67108863;
869
869
  var carry = r / 67108864 | 0;
870
870
  out.words[0] = lo;
871
- for (var k3 = 1; k3 < len; k3++) {
871
+ for (var k2 = 1; k2 < len; k2++) {
872
872
  var ncarry = carry >>> 26;
873
873
  var rword = carry & 67108863;
874
- var maxJ = Math.min(k3, num.length - 1);
875
- for (var j = Math.max(0, k3 - self.length + 1); j <= maxJ; j++) {
876
- var i = k3 - j | 0;
874
+ var maxJ = Math.min(k2, num.length - 1);
875
+ for (var j = Math.max(0, k2 - self.length + 1); j <= maxJ; j++) {
876
+ var i = k2 - j | 0;
877
877
  a = self.words[i] | 0;
878
878
  b2 = num.words[j] | 0;
879
879
  r = a * b2 + rword;
880
880
  ncarry += r / 67108864 | 0;
881
881
  rword = r & 67108863;
882
882
  }
883
- out.words[k3] = rword | 0;
883
+ out.words[k2] = rword | 0;
884
884
  carry = ncarry | 0;
885
885
  }
886
886
  if (carry !== 0) {
887
- out.words[k3] = carry | 0;
887
+ out.words[k2] = carry | 0;
888
888
  } else {
889
889
  out.length--;
890
890
  }
@@ -1450,13 +1450,13 @@ var require_bn = __commonJS({
1450
1450
  out.length = self.length + num.length;
1451
1451
  var carry = 0;
1452
1452
  var hncarry = 0;
1453
- for (var k3 = 0; k3 < out.length - 1; k3++) {
1453
+ for (var k2 = 0; k2 < out.length - 1; k2++) {
1454
1454
  var ncarry = hncarry;
1455
1455
  hncarry = 0;
1456
1456
  var rword = carry & 67108863;
1457
- var maxJ = Math.min(k3, num.length - 1);
1458
- for (var j = Math.max(0, k3 - self.length + 1); j <= maxJ; j++) {
1459
- var i = k3 - j;
1457
+ var maxJ = Math.min(k2, num.length - 1);
1458
+ for (var j = Math.max(0, k2 - self.length + 1); j <= maxJ; j++) {
1459
+ var i = k2 - j;
1460
1460
  var a = self.words[i] | 0;
1461
1461
  var b2 = num.words[j] | 0;
1462
1462
  var r = a * b2;
@@ -1468,12 +1468,12 @@ var require_bn = __commonJS({
1468
1468
  hncarry += ncarry >>> 26;
1469
1469
  ncarry &= 67108863;
1470
1470
  }
1471
- out.words[k3] = rword;
1471
+ out.words[k2] = rword;
1472
1472
  carry = ncarry;
1473
1473
  ncarry = hncarry;
1474
1474
  }
1475
1475
  if (carry !== 0) {
1476
- out.words[k3] = carry;
1476
+ out.words[k2] = carry;
1477
1477
  } else {
1478
1478
  out.length--;
1479
1479
  }
@@ -5479,13 +5479,13 @@ var require_ece = __commonJS({
5479
5479
  var MODE_DECRYPT = "decrypt";
5480
5480
  var keylog;
5481
5481
  if (process.env.ECE_KEYLOG === "1") {
5482
- keylog = function(m2, k3) {
5483
- console.warn(m2 + " [" + k3.length + "]: " + k3.toString("base64url"));
5484
- return k3;
5482
+ keylog = function(m2, k2) {
5483
+ console.warn(m2 + " [" + k2.length + "]: " + k2.toString("base64url"));
5484
+ return k2;
5485
5485
  };
5486
5486
  } else {
5487
- keylog = function(m2, k3) {
5488
- return k3;
5487
+ keylog = function(m2, k2) {
5488
+ return k2;
5489
5489
  };
5490
5490
  }
5491
5491
  function decode(b2) {
@@ -6559,8 +6559,8 @@ var require_node2 = __commonJS({
6559
6559
  exports.inspectOpts = Object.keys(process.env).filter((key) => {
6560
6560
  return /^debug_/i.test(key);
6561
6561
  }).reduce((obj, key) => {
6562
- const prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, (_2, k3) => {
6563
- return k3.toUpperCase();
6562
+ const prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, (_2, k2) => {
6563
+ return k2.toUpperCase();
6564
6564
  });
6565
6565
  let val = process.env[key];
6566
6566
  if (/^(yes|on|true|enabled)$/i.test(val)) {
@@ -6646,18 +6646,18 @@ var require_helpers = __commonJS({
6646
6646
  "node_modules/agent-base/dist/helpers.js"(exports) {
6647
6647
  "use strict";
6648
6648
  init_define_MESSENGER_BUILD_INFO();
6649
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m2, k3, k22) {
6650
- if (k22 === void 0) k22 = k3;
6651
- var desc = Object.getOwnPropertyDescriptor(m2, k3);
6649
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m2, k2, k22) {
6650
+ if (k22 === void 0) k22 = k2;
6651
+ var desc = Object.getOwnPropertyDescriptor(m2, k2);
6652
6652
  if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
6653
6653
  desc = { enumerable: true, get: function() {
6654
- return m2[k3];
6654
+ return m2[k2];
6655
6655
  } };
6656
6656
  }
6657
6657
  Object.defineProperty(o, k22, desc);
6658
- }) : (function(o, m2, k3, k22) {
6659
- if (k22 === void 0) k22 = k3;
6660
- o[k22] = m2[k3];
6658
+ }) : (function(o, m2, k2, k22) {
6659
+ if (k22 === void 0) k22 = k2;
6660
+ o[k22] = m2[k2];
6661
6661
  }));
6662
6662
  var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v2) {
6663
6663
  Object.defineProperty(o, "default", { enumerable: true, value: v2 });
@@ -6668,7 +6668,7 @@ var require_helpers = __commonJS({
6668
6668
  if (mod && mod.__esModule) return mod;
6669
6669
  var result = {};
6670
6670
  if (mod != null) {
6671
- for (var k3 in mod) if (k3 !== "default" && Object.prototype.hasOwnProperty.call(mod, k3)) __createBinding(result, mod, k3);
6671
+ for (var k2 in mod) if (k2 !== "default" && Object.prototype.hasOwnProperty.call(mod, k2)) __createBinding(result, mod, k2);
6672
6672
  }
6673
6673
  __setModuleDefault(result, mod);
6674
6674
  return result;
@@ -6717,18 +6717,18 @@ var require_dist = __commonJS({
6717
6717
  "node_modules/agent-base/dist/index.js"(exports) {
6718
6718
  "use strict";
6719
6719
  init_define_MESSENGER_BUILD_INFO();
6720
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m2, k3, k22) {
6721
- if (k22 === void 0) k22 = k3;
6722
- var desc = Object.getOwnPropertyDescriptor(m2, k3);
6720
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m2, k2, k22) {
6721
+ if (k22 === void 0) k22 = k2;
6722
+ var desc = Object.getOwnPropertyDescriptor(m2, k2);
6723
6723
  if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
6724
6724
  desc = { enumerable: true, get: function() {
6725
- return m2[k3];
6725
+ return m2[k2];
6726
6726
  } };
6727
6727
  }
6728
6728
  Object.defineProperty(o, k22, desc);
6729
- }) : (function(o, m2, k3, k22) {
6730
- if (k22 === void 0) k22 = k3;
6731
- o[k22] = m2[k3];
6729
+ }) : (function(o, m2, k2, k22) {
6730
+ if (k22 === void 0) k22 = k2;
6731
+ o[k22] = m2[k2];
6732
6732
  }));
6733
6733
  var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v2) {
6734
6734
  Object.defineProperty(o, "default", { enumerable: true, value: v2 });
@@ -6739,7 +6739,7 @@ var require_dist = __commonJS({
6739
6739
  if (mod && mod.__esModule) return mod;
6740
6740
  var result = {};
6741
6741
  if (mod != null) {
6742
- for (var k3 in mod) if (k3 !== "default" && Object.prototype.hasOwnProperty.call(mod, k3)) __createBinding(result, mod, k3);
6742
+ for (var k2 in mod) if (k2 !== "default" && Object.prototype.hasOwnProperty.call(mod, k2)) __createBinding(result, mod, k2);
6743
6743
  }
6744
6744
  __setModuleDefault(result, mod);
6745
6745
  return result;
@@ -6971,18 +6971,18 @@ var require_dist2 = __commonJS({
6971
6971
  "node_modules/https-proxy-agent/dist/index.js"(exports) {
6972
6972
  "use strict";
6973
6973
  init_define_MESSENGER_BUILD_INFO();
6974
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m2, k3, k22) {
6975
- if (k22 === void 0) k22 = k3;
6976
- var desc = Object.getOwnPropertyDescriptor(m2, k3);
6974
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m2, k2, k22) {
6975
+ if (k22 === void 0) k22 = k2;
6976
+ var desc = Object.getOwnPropertyDescriptor(m2, k2);
6977
6977
  if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
6978
6978
  desc = { enumerable: true, get: function() {
6979
- return m2[k3];
6979
+ return m2[k2];
6980
6980
  } };
6981
6981
  }
6982
6982
  Object.defineProperty(o, k22, desc);
6983
- }) : (function(o, m2, k3, k22) {
6984
- if (k22 === void 0) k22 = k3;
6985
- o[k22] = m2[k3];
6983
+ }) : (function(o, m2, k2, k22) {
6984
+ if (k22 === void 0) k22 = k2;
6985
+ o[k22] = m2[k2];
6986
6986
  }));
6987
6987
  var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v2) {
6988
6988
  Object.defineProperty(o, "default", { enumerable: true, value: v2 });
@@ -6993,7 +6993,7 @@ var require_dist2 = __commonJS({
6993
6993
  if (mod && mod.__esModule) return mod;
6994
6994
  var result = {};
6995
6995
  if (mod != null) {
6996
- for (var k3 in mod) if (k3 !== "default" && Object.prototype.hasOwnProperty.call(mod, k3)) __createBinding(result, mod, k3);
6996
+ for (var k2 in mod) if (k2 !== "default" && Object.prototype.hasOwnProperty.call(mod, k2)) __createBinding(result, mod, k2);
6997
6997
  }
6998
6998
  __setModuleDefault(result, mod);
6999
6999
  return result;
@@ -9656,10 +9656,10 @@ var require_extension = __commonJS({
9656
9656
  if (!Array.isArray(configurations)) configurations = [configurations];
9657
9657
  return configurations.map((params) => {
9658
9658
  return [extension2].concat(
9659
- Object.keys(params).map((k3) => {
9660
- let values = params[k3];
9659
+ Object.keys(params).map((k2) => {
9660
+ let values = params[k2];
9661
9661
  if (!Array.isArray(values)) values = [values];
9662
- return values.map((v2) => v2 === true ? k3 : `${k3}=${v2}`).join("; ");
9662
+ return values.map((v2) => v2 === true ? k2 : `${k2}=${v2}`).join("; ");
9663
9663
  })
9664
9664
  ).join("; ");
9665
9665
  }).join(", ");
@@ -11131,11 +11131,12 @@ function producerConfig(env = process.env) {
11131
11131
  }
11132
11132
  var NotificationOutbox = class {
11133
11133
  constructor(file, config, project, warn = () => {
11134
- }) {
11134
+ }, shouldRetire) {
11135
11135
  this.file = file;
11136
11136
  this.config = config;
11137
11137
  this.project = project;
11138
11138
  this.warn = warn;
11139
+ this.shouldRetire = shouldRetire;
11139
11140
  mkdirSync(dirname(file), { recursive: true, mode: 448 });
11140
11141
  const saved = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : { entries: [] };
11141
11142
  this.entries = Array.isArray(saved) ? saved : saved.entries;
@@ -11186,6 +11187,12 @@ var NotificationOutbox = class {
11186
11187
  this.persist([...this.entries, { id, value }], cursor);
11187
11188
  return true;
11188
11189
  }
11190
+ retireTarget(url, matches) {
11191
+ const id = `delete-target:${url}`;
11192
+ const retained = this.entries.filter((entry) => entry.operation === "delete-target" || !matches(entry.value, entry.payload));
11193
+ if (!retained.some((entry) => entry.id === id)) retained.push({ id, value: { url }, operation: "delete-target" });
11194
+ this.persist(retained);
11195
+ }
11189
11196
  drain() {
11190
11197
  return this.running ??= this.drainOnce().finally(() => {
11191
11198
  this.running = void 0;
@@ -11194,9 +11201,15 @@ var NotificationOutbox = class {
11194
11201
  async drainOnce() {
11195
11202
  for (const entry of [...this.entries].slice(0, 32)) {
11196
11203
  try {
11197
- const value = entry.payload ?? await this.project(entry.value, entry.id);
11198
- if (!entry.payload) this.persist(this.entries.map((e2) => e2.id === entry.id ? { ...e2, payload: value } : e2));
11199
- const response = await fetch(this.config.origin + "/api/v1/send", {
11204
+ if (!this.entries.some((current) => current.id === entry.id)) continue;
11205
+ const value = entry.operation === "delete-target" ? this.shouldRetire && !await this.shouldRetire(entry.value.url) ? void 0 : entry.value : entry.payload ?? await this.project(entry.value, entry.id);
11206
+ if (!this.entries.some((current) => current.id === entry.id)) continue;
11207
+ if (value === void 0) {
11208
+ this.persist(this.entries.filter((current) => current.id !== entry.id));
11209
+ continue;
11210
+ }
11211
+ if (!entry.operation && !entry.payload) this.persist(this.entries.map((e2) => e2.id === entry.id ? { ...e2, payload: value } : e2));
11212
+ const response = await fetch(this.config.origin + (entry.operation === "delete-target" ? "/api/v1/delete-target" : "/api/v1/send"), {
11200
11213
  method: "POST",
11201
11214
  redirect: "error",
11202
11215
  signal: AbortSignal.timeout(1e4),
@@ -11643,8 +11656,15 @@ var PushDeliveryQueue = class {
11643
11656
  var MessengerNotificationProducer = class {
11644
11657
  constructor(stateDir, config, identityCid, client, warn) {
11645
11658
  this.identityCid = identityCid;
11659
+ const knownContact = async (cid) => {
11660
+ if (!client.listContacts) return void 0;
11661
+ const view = await client.listContacts();
11662
+ if (!Array.isArray(view.contacts) || !Array.isArray(view.pending) || [...view.contacts, ...view.pending].some((contact) => typeof contact?.container_id !== "string")) throw Error("Contact inventory unavailable");
11663
+ return [...view.contacts, ...view.pending].some((contact) => contact.container_id.toLowerCase() === cid.toLowerCase());
11664
+ };
11646
11665
  this.outbox = new NotificationOutbox(join(stateDir, "notification-outbox.json"), config, async (value, eventId) => {
11647
11666
  const record = value;
11667
+ if (await knownContact(record.sender_id) === false) return;
11648
11668
  const event = await projectPushEvent(client, {
11649
11669
  contactId: record.sender_id,
11650
11670
  wireId: record.wire_id,
@@ -11657,7 +11677,7 @@ var MessengerNotificationProducer = class {
11657
11677
  body: event.body.slice(0, 512) || "New message",
11658
11678
  url: `/fleet/chats?source=messenger&chat=${encodeURIComponent(record.sender_id)}&detail=1#chat-message-${encodeURIComponent(record.wire_id)}`
11659
11679
  };
11660
- }, warn);
11680
+ }, warn, async (url) => await knownContact(new URL(url, "https://ours.invalid").searchParams.get("chat")) !== true);
11661
11681
  }
11662
11682
  outbox;
11663
11683
  admit(record) {
@@ -11670,6 +11690,21 @@ var MessengerNotificationProducer = class {
11670
11690
  });
11671
11691
  return { status: admitted ? "queued" : "saturated" };
11672
11692
  }
11693
+ retireContact(cid) {
11694
+ const url = `/fleet/chats?source=messenger&chat=${encodeURIComponent(cid)}`;
11695
+ this.outbox.retireTarget(url, (value, payload) => {
11696
+ const record = value;
11697
+ if (typeof record?.sender_id === "string" && record.sender_id.toLowerCase() === cid.toLowerCase()) return true;
11698
+ if (!payload) return false;
11699
+ try {
11700
+ const target = new URL(payload.url, "https://ours.invalid");
11701
+ return target.pathname === "/fleet/chats" && target.searchParams.get("source") === "messenger" && target.searchParams.get("chat")?.toLowerCase() === cid.toLowerCase();
11702
+ } catch {
11703
+ return false;
11704
+ }
11705
+ });
11706
+ void this.outbox.drain();
11707
+ }
11673
11708
  close() {
11674
11709
  return this.outbox.close();
11675
11710
  }
@@ -20999,7 +21034,13 @@ var ROUTES = {
20999
21034
  // operation: roots ride along on the contacts view.
21000
21035
  "GET /api/contacts/roots": async ({ client }) => ({ roots: (await client.listContacts()).roots }),
21001
21036
  "POST /api/contacts/add": async ({ client, body }) => client.addContact({ invite: str(body, "invite"), name: optStr(body, "name") }),
21002
- "POST /api/contacts/remove": async ({ client, body }) => client.removeContact({ contact: str(body, "contact") }),
21037
+ "POST /api/contacts/remove": async ({ client, body, deps }) => {
21038
+ const contact = str(body, "contact");
21039
+ const canonical3 = deps.retireContactNotifications ? (await resolveContact(client, contact)).cid : contact;
21040
+ const result = await client.removeContact({ contact: canonical3 });
21041
+ deps.retireContactNotifications?.(canonical3);
21042
+ return result;
21043
+ },
21003
21044
  "POST /api/contacts/rename": async ({ client, body }) => client.renameContact({ contact: str(body, "contact"), name: str(body, "name") }),
21004
21045
  "GET /api/contacts/local-book": async ({ client }) => client.listLocalContactBook(),
21005
21046
  "POST /api/contacts/local-book/policy": async ({ client, body }) => client.setLocalBookPolicy({ expose: optBool(body, "expose"), auto_accept: optBool(body, "auto_accept") }),
@@ -21038,13 +21079,38 @@ var ROUTES = {
21038
21079
  return human.rootCid === void 0 ? { cid: identity.cid, preserveProfile: true } : { cid: identity.cid, rootCid: human.rootCid, preserveProfile: true };
21039
21080
  },
21040
21081
  // First-party setup operation: daemon signs the command using this bound root.
21082
+ "POST /api/workspace/unregister": async ({ client, body }) => {
21083
+ const identity = await client.currentIdentity();
21084
+ if (!identity.isRoot || identity.temporary) throw bad("Workspace retirement requires the bound Human root");
21085
+ const rootCid = str(body, "rootCid").toUpperCase(), serverCid = str(body, "serverCid").toUpperCase();
21086
+ if (!/^[A-F0-9]{64}$/.test(rootCid) || !/^[A-F0-9]{64}$/.test(serverCid) || identity.cid.toUpperCase() !== rootCid) throw bad("Workspace retirement identity mismatch");
21087
+ const command = { type: "ours.app.unregister-workspace.v1" };
21088
+ for (const key of ["workspaceId", "hostWorkspaceId", "operationNonce"]) {
21089
+ const value = str(body, key);
21090
+ if (!/^[A-Za-z0-9_-]{43}$/.test(value)) throw bad("Invalid workspace retirement");
21091
+ command[key] = value;
21092
+ }
21093
+ if (body.operationExpiresAt !== void 0) {
21094
+ if (!Number.isSafeInteger(body.operationExpiresAt) || Number(body.operationExpiresAt) <= 0 || Number(body.operationExpiresAt) > Date.now() + 864e5) throw bad("Invalid retirement deadline");
21095
+ command.operationExpiresAt = Number(body.operationExpiresAt);
21096
+ }
21097
+ if (body.replacement !== void 0) {
21098
+ const replacement = body.replacement;
21099
+ if (!replacement || typeof replacement !== "object" || Object.keys(replacement).length !== 3 || !["workspaceId", "accountId", "nonce"].every((key) => typeof replacement[key] === "string" && /^[A-Za-z0-9_-]{43}$/.test(replacement[key]))) throw bad("Invalid replacement challenge");
21100
+ command.replacement = Object.fromEntries(["workspaceId", "accountId", "nonce"].map((key) => [key, replacement[key]]));
21101
+ }
21102
+ if (!(await client.listContacts()).contacts.some((contact) => contact.container_id.toUpperCase() === serverCid)) throw bad("Original enrollment contact is not ready");
21103
+ const outcome = await client.sendCommand({ contact: serverCid, command: "unregister-workspace", arguments: command });
21104
+ if (!("sent" in outcome) || !outcome.sent) throw bad("Workspace retirement was not sent");
21105
+ return { submitted: true, rootCid: identity.cid };
21106
+ },
21041
21107
  "POST /api/workspace/enroll": async ({ client, deps, body }) => {
21042
21108
  const identity = await client.currentIdentity();
21043
21109
  if (!identity.isRoot || identity.temporary) throw bad("Workspace proof requires the bound Human root");
21044
21110
  const serverCid = str(body, "serverCid").toUpperCase();
21045
21111
  if (!/^[A-F0-9]{64}$/.test(serverCid) || !/^[A-Za-z0-9_-]{43}$/.test(str(body, "hostWorkspaceId"))) throw bad("Invalid workspace proof");
21046
21112
  const challenge = body.challenge;
21047
- if (!challenge || !["nonce", "workspaceId", "accountId"].every((k3) => typeof challenge[k3] === "string" && /^[A-Za-z0-9_-]{43}$/.test(String(challenge[k3]))) || !Number.isFinite(challenge.expiresAt) || Number(challenge.expiresAt) <= Date.now() || Number(challenge.expiresAt) > Date.now() + 16 * 6e4) throw bad("Workspace challenge expired or invalid");
21113
+ if (!challenge || !["nonce", "workspaceId", "accountId"].every((k2) => typeof challenge[k2] === "string" && /^[A-Za-z0-9_-]{43}$/.test(String(challenge[k2]))) || !Number.isFinite(challenge.expiresAt) || Number(challenge.expiresAt) <= Date.now() || Number(challenge.expiresAt) > Date.now() + 16 * 6e4) throw bad("Workspace challenge expired or invalid");
21048
21114
  const preserveProfile = optBool(body, "preserveProfile") === true;
21049
21115
  let humanProfile;
21050
21116
  if (!preserveProfile) try {
@@ -21055,8 +21121,11 @@ var ROUTES = {
21055
21121
  const invitation = str(body, "invitation");
21056
21122
  if (invitation.length > 8192) throw bad("Workspace invitation too large");
21057
21123
  if (humanProfile) writeHumanProfile(deps.config.stateDir, deps.identityCid, humanProfile);
21058
- const peer = await client.addContact({ invite: invitation });
21059
- if (peer.cid.toUpperCase() !== serverCid) throw bad("Enrollment server identity mismatch");
21124
+ const connected = preserveProfile && (await client.listContacts()).contacts.some((contact) => contact.container_id.toUpperCase() === serverCid);
21125
+ if (!connected) {
21126
+ const peer = await client.addContact({ invite: invitation });
21127
+ if (peer.cid.toUpperCase() !== serverCid) throw bad("Enrollment server identity mismatch");
21128
+ }
21060
21129
  const contactDeadline = Date.now() + 1e4;
21061
21130
  while (!(await client.listContacts()).contacts.some((contact) => contact.container_id.toUpperCase() === serverCid)) {
21062
21131
  if (Date.now() >= contactDeadline) throw bad("Enrollment contact is not ready; obtain a fresh setup payload before retrying");
@@ -21200,15 +21269,15 @@ var ROUTES = {
21200
21269
  if (keys === null || typeof keys !== "object" || Array.isArray(keys)) {
21201
21270
  throw bad("keys must be an object with p256dh and auth");
21202
21271
  }
21203
- const k3 = keys;
21204
- if (typeof k3.p256dh !== "string" || typeof k3.auth !== "string") {
21272
+ const k2 = keys;
21273
+ if (typeof k2.p256dh !== "string" || typeof k2.auth !== "string") {
21205
21274
  throw bad("keys.p256dh and keys.auth must be strings");
21206
21275
  }
21207
21276
  let saved;
21208
21277
  try {
21209
21278
  saved = deps.push.ensure({
21210
21279
  endpoint: str(body, "endpoint"),
21211
- keys: { p256dh: k3.p256dh, auth: k3.auth },
21280
+ keys: { p256dh: k2.p256dh, auth: k2.auth },
21212
21281
  label: optStr(body, "label"),
21213
21282
  preview: body.preview === void 0 ? void 0 : str(body, "preview"),
21214
21283
  bindingId: optStr(body, "binding_id")
@@ -21347,18 +21416,18 @@ import { randomBytes as randomBytes2 } from "node:crypto";
21347
21416
  // node_modules/@ours.network/sdk/dist/index.js
21348
21417
  init_define_MESSENGER_BUILD_INFO();
21349
21418
 
21350
- // node_modules/@ours.network/sdk/dist/chunk-H6U3YRJE.js
21419
+ // node_modules/@ours.network/sdk/dist/chunk-VJJU7HLN.js
21351
21420
  init_define_MESSENGER_BUILD_INFO();
21352
21421
 
21353
- // node_modules/@ours.network/sdk/dist/chunk-TQF3UMFZ.js
21422
+ // node_modules/@ours.network/sdk/dist/chunk-SHYFSAAM.js
21354
21423
  init_define_MESSENGER_BUILD_INFO();
21355
21424
  import * as e from "node:fs";
21356
- import { randomBytes as g } from "node:crypto";
21357
- import { dirname as p, isAbsolute as _, resolve as w } from "node:path";
21358
- import { homedir as v } from "node:os";
21359
- import { resolve as b, join as A, dirname as h, basename as R } from "node:path";
21425
+ import { randomBytes as m } from "node:crypto";
21426
+ import { dirname as p, isAbsolute as b, resolve as v } from "node:path";
21427
+ import { homedir as T } from "node:os";
21428
+ import { resolve as _, join as A, dirname as h, basename as R } from "node:path";
21360
21429
  function u(t, o = false) {
21361
- if (typeof process.geteuid != "function" || !e.constants.O_NOFOLLOW || !e.constants.O_NONBLOCK || typeof t != "string" || !_(t) || w(t) !== t) throw new Error();
21430
+ if (typeof process.geteuid != "function" || !e.constants.O_NOFOLLOW || !e.constants.O_NONBLOCK || typeof t != "string" || !b(t) || v(t) !== t) throw new Error();
21362
21431
  let s = process.geteuid(), r = [];
21363
21432
  for (let i = p(t); r.unshift(i), i !== p(i); i = p(i)) ;
21364
21433
  return r.map((i, l) => {
@@ -21375,36 +21444,36 @@ function u(t, o = false) {
21375
21444
  n = e.lstatSync(i);
21376
21445
  }
21377
21446
  if (!n.isDirectory() || n.uid !== 0 && n.uid !== s) throw new Error();
21378
- let c = n.uid === 0 && (n.mode & 512) !== 0;
21379
- if (n.mode & 18 && !c) throw new Error();
21447
+ let a = n.uid === 0 && (n.mode & 512) !== 0;
21448
+ if (n.mode & 18 && !a) throw new Error();
21380
21449
  if (l === r.length - 1 && (n.uid !== s || (n.mode & 4095) !== 448)) throw new Error();
21381
21450
  return `${n.dev}:${n.ino}:${n.uid}:${n.gid}:${n.mode}`;
21382
21451
  }).join("|");
21383
21452
  }
21384
- function m(t, o = 65536, s = false) {
21453
+ function O(t, o = 65536, s = false) {
21385
21454
  let r;
21386
21455
  try {
21387
- let i = u(t), l = process.geteuid(), n = (a) => {
21388
- if (!a.isFile() || a.uid !== l || (a.mode & 4095) !== 384 || a.size < 1 || a.size > o) throw new Error();
21389
- return `${a.dev}:${a.ino}:${a.uid}:${a.gid}:${a.mode}:${a.size}:${a.mtimeMs}:${a.ctimeMs}`;
21390
- }, c = n(e.lstatSync(t));
21456
+ let i = u(t), l = process.geteuid(), n = (c) => {
21457
+ if (!c.isFile() || c.uid !== l || (c.mode & 4095) !== 384 || c.size < 1 || c.size > o) throw new Error();
21458
+ return `${c.dev}:${c.ino}:${c.uid}:${c.gid}:${c.mode}:${c.size}:${c.mtimeMs}:${c.ctimeMs}`;
21459
+ }, a = n(e.lstatSync(t));
21391
21460
  r = e.openSync(t, e.constants.O_RDONLY | e.constants.O_NOFOLLOW | e.constants.O_NONBLOCK);
21392
21461
  let d2 = e.fstatSync(r);
21393
- if (n(d2) !== c || u(t) !== i || n(e.lstatSync(t)) !== c) throw new Error();
21462
+ if (n(d2) !== a || u(t) !== i || n(e.lstatSync(t)) !== a) throw new Error();
21394
21463
  let f = Buffer.alloc(d2.size);
21395
- if (e.readSync(r, f, 0, f.length, 0) !== f.length || n(e.fstatSync(r)) !== c || u(t) !== i || n(e.lstatSync(t)) !== c) throw new Error();
21464
+ if (e.readSync(r, f, 0, f.length, 0) !== f.length || n(e.fstatSync(r)) !== a || u(t) !== i || n(e.lstatSync(t)) !== a) throw new Error();
21396
21465
  if (s) {
21397
21466
  e.fsyncSync(r);
21398
- for (let a = p(t); ; a = p(a)) {
21399
- let S = e.openSync(a, e.constants.O_RDONLY | e.constants.O_DIRECTORY | e.constants.O_NOFOLLOW);
21467
+ for (let c = p(t); ; c = p(c)) {
21468
+ let S = e.openSync(c, e.constants.O_RDONLY | e.constants.O_DIRECTORY | e.constants.O_NOFOLLOW);
21400
21469
  try {
21401
21470
  e.fsyncSync(S);
21402
21471
  } finally {
21403
21472
  e.closeSync(S);
21404
21473
  }
21405
- if (a === p(a)) break;
21474
+ if (c === p(c)) break;
21406
21475
  }
21407
- if (u(t) !== i || n(e.lstatSync(t)) !== c) throw new Error();
21476
+ if (u(t) !== i || n(e.lstatSync(t)) !== a) throw new Error();
21408
21477
  }
21409
21478
  return f;
21410
21479
  } catch {
@@ -21413,24 +21482,24 @@ function m(t, o = 65536, s = false) {
21413
21482
  r !== void 0 && e.closeSync(r);
21414
21483
  }
21415
21484
  }
21416
- function T(t, o) {
21485
+ function w(t, o) {
21417
21486
  let s, r;
21418
21487
  try {
21419
21488
  try {
21420
- return e.lstatSync(t), { token: O(t), source: "file" };
21489
+ return e.lstatSync(t), { token: g(t), source: "file" };
21421
21490
  } catch (f) {
21422
21491
  if (f.code !== "ENOENT") throw f;
21423
21492
  }
21424
21493
  if (!o) return null;
21425
- let i = u(t, true), l = g(32).toString("hex"), n = `${t}.${g(16).toString("hex")}.tmp`;
21494
+ let i = u(t, true), l = m(32).toString("hex"), n = `${t}.${m(16).toString("hex")}.tmp`;
21426
21495
  if (r = e.openSync(n, e.constants.O_WRONLY | e.constants.O_CREAT | e.constants.O_EXCL | e.constants.O_NOFOLLOW, 384), s = n, e.fchmodSync(r, 384), e.writeFileSync(r, `${l}
21427
21496
  `), e.fsyncSync(r), e.closeSync(r), r = void 0, u(t) !== i) throw new Error();
21428
- let c = "generated";
21497
+ let a = "generated";
21429
21498
  try {
21430
21499
  e.linkSync(s, t);
21431
21500
  } catch (f) {
21432
21501
  if (f.code !== "EEXIST") throw f;
21433
- c = "file";
21502
+ a = "file";
21434
21503
  }
21435
21504
  e.unlinkSync(s), s = void 0;
21436
21505
  let d2 = e.openSync(p(t), e.constants.O_RDONLY | e.constants.O_DIRECTORY | e.constants.O_NOFOLLOW);
@@ -21439,29 +21508,29 @@ function T(t, o) {
21439
21508
  } finally {
21440
21509
  e.closeSync(d2);
21441
21510
  }
21442
- return { token: O(t), source: c };
21511
+ return { token: g(t), source: a };
21443
21512
  } catch {
21444
21513
  throw new Error("Credential publication or reuse failed; no in-memory fallback or automatic repair");
21445
21514
  } finally {
21446
21515
  r !== void 0 && e.closeSync(r), s !== void 0 && e.unlinkSync(s);
21447
21516
  }
21448
21517
  }
21449
- function O(t) {
21450
- let o = m(t, 4096).toString("utf8");
21518
+ function g(t) {
21519
+ let o = O(t, 4096).toString("utf8");
21451
21520
  if (!/^[\x21-\x7e]+(?:\r?\n)?$/.test(o)) throw new Error("Credential file contains an invalid token");
21452
21521
  return o.trimEnd();
21453
21522
  }
21454
- var k = 100 * 1024 * 1024;
21455
- var $2 = { brokerUrl: "wss://broker1.ours.network", port: 3050, stateDir: b(v(), ".ours"), gcIntervalMs: 36e5, historyMaxBytes: k, apiVisibility: "owner", apiAuthMode: "legacy" };
21523
+ var I = 100 * 1024 * 1024;
21524
+ var $2 = { brokerUrl: "wss://broker1.ours.network", port: 3050, stateDir: _(T(), ".ours"), gcIntervalMs: 36e5, historyMaxBytes: I, apiVisibility: "owner", apiAuthMode: "legacy" };
21456
21525
  var K = "daemon-token";
21457
21526
 
21458
- // node_modules/@ours.network/sdk/dist/chunk-H6U3YRJE.js
21527
+ // node_modules/@ours.network/sdk/dist/chunk-VJJU7HLN.js
21459
21528
  import { homedir as me } from "node:os";
21460
21529
  import { isAbsolute as ye, join as ge, resolve as ne } from "node:path";
21461
21530
  import { randomBytes as V } from "node:crypto";
21462
21531
  import { readFileSync as Ee } from "node:fs";
21463
21532
  import { homedir as ve } from "node:os";
21464
- import { join as M, resolve as P } from "node:path";
21533
+ import { join as M2, resolve as P } from "node:path";
21465
21534
  var d = class extends Error {
21466
21535
  constructor(n, r, i) {
21467
21536
  super(r);
@@ -21555,7 +21624,7 @@ var x = class {
21555
21624
  explicitApiToken;
21556
21625
  credentialPath;
21557
21626
  get apiToken() {
21558
- return this.credentialPath === void 0 ? this.explicitApiToken : O(this.credentialPath);
21627
+ return this.credentialPath === void 0 ? this.explicitApiToken : g(this.credentialPath);
21559
21628
  }
21560
21629
  fetchImpl;
21561
21630
  commandHandlers = /* @__PURE__ */ new Map();
@@ -21897,7 +21966,7 @@ function we(e2) {
21897
21966
  function re(e2 = process.env) {
21898
21967
  let t = he(e2), n;
21899
21968
  try {
21900
- n = JSON.parse(m(t).toString("utf8"));
21969
+ n = JSON.parse(O(t).toString("utf8"));
21901
21970
  } catch {
21902
21971
  throw new O2("selected profile is missing, invalid JSON or unsafe; require a 0700 directory and a 0600 regular file");
21903
21972
  }
@@ -21928,13 +21997,13 @@ function Oe(e2 = {}) {
21928
21997
  let t = e2.env ?? process.env;
21929
21998
  if (t.OURS_TLS_CERT !== void 0 || t.OURS_TLS_KEY !== void 0) throw new p2("INCOHERENT_SELECTION", "Daemon TLS is not supported in V1; remove OURS_TLS_CERT/OURS_TLS_KEY and explicitly configure HTTP");
21930
21999
  if (e2.endpoint !== void 0 && new URL(e2.endpoint).protocol !== "http:" && new URL(e2.endpoint).protocol !== "https:") throw new p2("INCOHERENT_SELECTION", "Client requires an HTTP or HTTPS daemon endpoint");
21931
- let n = e2.readTextFile ?? ((E) => Ee(E, "utf8")), r = e2.homeDir ?? ve(), i = k2(t, "OURS_INSTANCE");
22000
+ let n = e2.readTextFile ?? ((E) => Ee(E, "utf8")), r = e2.homeDir ?? ve(), i = k(t, "OURS_INSTANCE");
21932
22001
  if (i !== void 0) throw new p2("INCOHERENT_SELECTION", `OURS_INSTANCE=${JSON.stringify(i)} is set, but named daemon instances are not implemented. Select the daemon with an explicit endpoint/port option or with OURS_PORT, and unset OURS_INSTANCE.`);
21933
22002
  if (e2.stateDir !== void 0 && e2.expectStateDir !== void 0) {
21934
22003
  let E = P(e2.stateDir), T2 = P(e2.expectStateDir);
21935
22004
  if (E !== T2) throw new p2("INCOHERENT_SELECTION", `stateDir ${JSON.stringify(E)} and expectStateDir ${JSON.stringify(T2)} name different directories. The state directory decides which token is read; the expected one decides which daemon that token may be sent to. Reconcile them, or pass only one.`);
21936
22005
  }
21937
- let o = Re(e2.configPath, k2(t, "OURS_CONFIG")), s = o ? Se(o.value, n) : {}, a = ie(e2.port, Ae(k2(t, "OURS_PORT")), s.port, oe), c = ie(e2.stateDir ?? e2.expectStateDir, k2(t, "OURS_STATE_DIR"), s.stateDir, M(r, _e)), l = { value: P(c.value), source: c.source }, m2 = e2.endpoint !== void 0 ? { value: y(e2.endpoint), source: "explicit" } : { value: `http://127.0.0.1:${a.value}`, source: a.source }, f = k2(t, "OURS_API_TOKEN");
22006
+ let o = Re(e2.configPath, k(t, "OURS_CONFIG")), s = o ? Se(o.value, n) : {}, a = ie(e2.port, Ae(k(t, "OURS_PORT")), s.port, oe), c = ie(e2.stateDir ?? e2.expectStateDir, k(t, "OURS_STATE_DIR"), s.stateDir, M2(r, _e)), l = { value: P(c.value), source: c.source }, m2 = e2.endpoint !== void 0 ? { value: y(e2.endpoint), source: "explicit" } : { value: `http://127.0.0.1:${a.value}`, source: a.source }, f = k(t, "OURS_API_TOKEN");
21938
22007
  Te({ endpointSelected: e2.endpoint !== void 0 || a.source !== "default", endpointSource: m2.source, stateDirIntentional: Ie.includes(l.source), stateDirSource: l.source, hasExplicitToken: e2.token !== void 0 && e2.token !== "", hasEnvToken: f !== void 0, requireExplicitSelection: e2.requireExplicitSelection === true, portSource: a.source });
21939
22008
  let g2 = be(e2.token, f, s, l, e2.readTextFile);
21940
22009
  return { baseUrl: m2, port: a, stateDir: l, expectStateDir: e2.expectStateDir !== void 0 ? P(e2.expectStateDir) : l.value, ...g2 ? { token: g2 } : {}, ...o ? { configPath: o } : {} };
@@ -21957,11 +22026,11 @@ function be(e2, t, n, r, i) {
21957
22026
  let o = n.apiToken?.trim();
21958
22027
  if (o) return { value: o, source: "config-file" };
21959
22028
  if (i === void 0) {
21960
- let s = T(M(r.value, J), false);
22029
+ let s = w(M2(r.value, J), false);
21961
22030
  return s ? { value: s.token, source: "token-file" } : void 0;
21962
22031
  }
21963
22032
  try {
21964
- let s = i(M(r.value, J)).trim();
22033
+ let s = i(M2(r.value, J)).trim();
21965
22034
  if (s) return { value: s, source: "token-file" };
21966
22035
  } catch {
21967
22036
  }
@@ -22002,7 +22071,7 @@ function Se(e2, t) {
22002
22071
  }
22003
22072
  return o;
22004
22073
  }
22005
- function k2(e2, t) {
22074
+ function k(e2, t) {
22006
22075
  let n = e2[t];
22007
22076
  if (n === void 0) return;
22008
22077
  let r = n.trim();
@@ -22063,18 +22132,18 @@ async function G(e2 = {}) {
22063
22132
  if (i === "external") _2.includes("external-sessions-v1") || _2.push("external-sessions-v1");
22064
22133
  else if (_2.includes("external-sessions-v1")) throw new Error("External capabilities require explicit sessionMode: external; refusing local-mode fallback");
22065
22134
  {
22066
- let b2 = await globalThis.fetch(u2(T2, "/selection"), { signal: s ? AbortSignal.any([s, AbortSignal.timeout(5e3)]) : AbortSignal.timeout(5e3), redirect: "error" }), I = await F(b2);
22067
- if (!b2.ok || !I || Object.keys(I).length !== 3 || Object.keys(I).some((S) => !["schema", "instanceId", "capabilities"].includes(S)) || I.schema !== 1 || I.instanceId !== a) throw new Error("Daemon instance selection metadata is absent, incompatible or mismatched");
22068
- let j = I.capabilities;
22135
+ let b2 = await globalThis.fetch(u2(T2, "/selection"), { signal: s ? AbortSignal.any([s, AbortSignal.timeout(5e3)]) : AbortSignal.timeout(5e3), redirect: "error" }), I2 = await F(b2);
22136
+ if (!b2.ok || !I2 || Object.keys(I2).length !== 3 || Object.keys(I2).some((S) => !["schema", "instanceId", "capabilities"].includes(S)) || I2.schema !== 1 || I2.instanceId !== a) throw new Error("Daemon instance selection metadata is absent, incompatible or mismatched");
22137
+ let j = I2.capabilities;
22069
22138
  if (!Array.isArray(j) || j.some((S) => typeof S != "string") || _2.some((S) => !j.includes(S))) throw new Error("Daemon does not support the required capabilities; refusing local-mode fallback");
22070
22139
  }
22071
- let H = l === void 0 ? E : O(l);
22140
+ let H = l === void 0 ? E : g(l);
22072
22141
  if (typeof H != "string" || !H.trim()) throw new Error("Instance selection requires an explicit API token after daemon verification");
22073
22142
  return new x({ url: T2, sessionMode: i, requestSignal: s, ...l === void 0 ? { apiToken: H } : { credentialPath: l }, leaseToken: n ?? V(32).toString("hex"), clientPid: r });
22074
22143
  }
22075
22144
  if (c !== void 0) throw new Error("Capability selection requires an expected daemon instance");
22076
22145
  let f = Oe(m2);
22077
- return await Ne(f, { fetch: o }), new x({ url: f.baseUrl.value, leaseToken: n ?? V(32).toString("hex"), clientPid: r, ...f.token?.source === "token-file" && m2.readTextFile === void 0 ? { credentialPath: M(f.expectStateDir, J) } : { apiToken: f.token?.value }, fetch: o, requestSignal: s });
22146
+ return await Ne(f, { fetch: o }), new x({ url: f.baseUrl.value, leaseToken: n ?? V(32).toString("hex"), clientPid: r, ...f.token?.source === "token-file" && m2.readTextFile === void 0 ? { credentialPath: M2(f.expectStateDir, J) } : { apiToken: f.token?.value }, fetch: o, requestSignal: s });
22078
22147
  }
22079
22148
 
22080
22149
  // src/owner-session.ts
@@ -23667,7 +23736,8 @@ async function start(cfg, buildInfo, dependencies = DEFAULT_START_DEPENDENCIES)
23667
23736
  presenceSockets: presence.socketCount
23668
23737
  }),
23669
23738
  events,
23670
- identityCid: bound.cid
23739
+ identityCid: bound.cid,
23740
+ retireContactNotifications: notificationOutbox ? (cid) => notificationOutbox.retireContact(cid) : void 0
23671
23741
  };
23672
23742
  const moduleDir = dirname5(fileURLToPath(import.meta.url));
23673
23743
  const appCandidates = [
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  normalizeBasePath,
8
8
  operatorError,
9
9
  reportFailure
10
- } from "./chunks/chunk-XNYNQSPB.js";
10
+ } from "./chunks/chunk-NB6YYTBL.js";
11
11
 
12
12
  // src/cli.ts
13
13
  init_define_MESSENGER_BUILD_INFO();
@@ -131,7 +131,7 @@ async function main() {
131
131
  }
132
132
  if (process.argv.length !== 3) throw new ConfigurationError("serve does not accept command-line options");
133
133
  const cfg = loadConfig();
134
- const { start } = await import("./chunks/server-ML4SVL44.js");
134
+ const { start } = await import("./chunks/server-QMB6SIVE.js");
135
135
  const handle = await start(cfg, BUILD_INFO);
136
136
  let closing = false;
137
137
  const shutdown = (signal) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/messenger-server",
3
- "version": "1.0.31-nightly.14",
3
+ "version": "1.0.31-nightly.16",
4
4
  "description": "Self-hosted Messenger REST/SSE backend for one identity on a shared Ours daemon.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -33,20 +33,20 @@
33
33
  "typecheck": "tsc -p tsconfig.json --noEmit",
34
34
  "test:startup": "node --import tsx tests/startup-readiness.test.mjs",
35
35
  "test:runtime-catalog-candidate": "node --import tsx tests/runtime-command-catalog-reopen-e2e.test.mjs",
36
- "test:offline": "for f in tests/workspace-profile-preservation.test.mjs tests/notification-producer.test.mjs tests/base-path.test.mjs tests/build-epoch.test.mjs tests/owner-session.test.mjs tests/v1-runtime.test.mjs tests/typed-commands-core.test.mjs tests/shared-daemon-contract.test.mjs tests/room-envelope-matrix.test.mjs tests/bundle-smoke.test.mjs tests/build-metadata.test.mjs tests/production-security.test.mjs tests/conversation-page.test.mjs tests/media-response.test.mjs tests/html-preview-transform.test.mjs tests/html-preview-response.test.mjs tests/push-store.test.mjs tests/push-store-hardening.test.mjs tests/push-delivery.test.mjs tests/push-queue-saturation.test.mjs tests/events.test.mjs tests/watch-cursor.test.mjs tests/watch-reconcile.test.mjs tests/introduction-send-guard.test.mjs tests/sse-e2e.test.mjs tests/presence.test.mjs tests/static-hosting.test.mjs tests/room-preview.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
36
+ "test:offline": "for f in tests/workspace-profile-preservation.test.mjs tests/notification-producer.test.mjs tests/notification-contact-retirement.test.mjs tests/workspace-root-retirement.test.mjs tests/base-path.test.mjs tests/build-epoch.test.mjs tests/owner-session.test.mjs tests/v1-runtime.test.mjs tests/typed-commands-core.test.mjs tests/shared-daemon-contract.test.mjs tests/room-envelope-matrix.test.mjs tests/bundle-smoke.test.mjs tests/build-metadata.test.mjs tests/production-security.test.mjs tests/conversation-page.test.mjs tests/media-response.test.mjs tests/html-preview-transform.test.mjs tests/html-preview-response.test.mjs tests/push-store.test.mjs tests/push-store-hardening.test.mjs tests/push-delivery.test.mjs tests/push-queue-saturation.test.mjs tests/events.test.mjs tests/watch-cursor.test.mjs tests/watch-reconcile.test.mjs tests/introduction-send-guard.test.mjs tests/sse-e2e.test.mjs tests/presence.test.mjs tests/static-hosting.test.mjs tests/room-preview.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
37
37
  "test:loopback": "for f in tests/shared-daemon-e2e.test.mjs tests/typed-command-loopback-e2e.test.mjs tests/introduction-send.test.mjs tests/rest-e2e.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
38
38
  "test": "npm run build && npm run test:startup && npm run test:offline && npm run test:loopback"
39
39
  },
40
40
  "dependencies": {
41
41
  "@eslint-community/regexpp": "4.12.2",
42
- "@ours.network/sdk": "3.8.1-nightly.15",
42
+ "@ours.network/sdk": "3.8.1-nightly.16",
43
43
  "parse5": "^8.0.1",
44
44
  "web-push": "^3.6.7",
45
45
  "ws": "^8.21.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@ours.network/cli": "2.8.1-nightly.13",
49
- "@ours.network/daemon": "3.8.1-nightly.7",
48
+ "@ours.network/cli": "2.8.1-nightly.14",
49
+ "@ours.network/daemon": "3.8.1-nightly.8",
50
50
  "@playwright/test": "^1.62.1",
51
51
  "@types/node": "^20.14.0",
52
52
  "@types/web-push": "^3.6.4",