@semiont/make-meaning 0.5.32 → 0.5.33

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.
@@ -10047,36 +10047,56 @@ var Stower = class {
10047
10047
  *
10048
10048
  * Appends are sequential, not concurrent: the event log is the system of
10049
10049
  * record and a batch that half-lands under concurrency is harder to reason
10050
- * about than one that stops at the first failure. A partial batch is
10051
- * reported as a failure and the worker retries the WHOLE unit, which is safe
10052
- * because ids are deterministic (P3) and the annotation fold is idempotent
10053
- * by id re-appending what already landed changes nothing.
10050
+ * about than one that stops at the first failure.
10051
+ *
10052
+ * This channel is AT-LEAST-ONCE, and the log must not grow on a repeat
10053
+ * (COMMIT-ACK-FALSE-FAILURE F3). Two paths re-send a batch that already
10054
+ * landed: an acknowledgement lost after a successful append (the unit is
10055
+ * never checkpointed, so the retry re-runs exactly the unit that landed), and
10056
+ * a partial batch, reported as a failure and retried whole. Deterministic ids
10057
+ * (JOB-RESTART-SAFETY P3) made those safe for the PROJECTIONS — the resource
10058
+ * view and the graph both refuse a duplicate id — but a projection's guard
10059
+ * says nothing about the log, which appends whatever it is handed. The result
10060
+ * was a green graph over a doubled log: silent, and not undoable.
10061
+ *
10062
+ * So the batch is diffed against what the resource already holds. ONE view
10063
+ * read per commit, never per annotation: the view for a 1,673-annotation
10064
+ * resource is ~3 MB, and re-reading it per append would cost gigabytes of
10065
+ * parsing for a single job.
10054
10066
  */
10055
10067
  async handleMarkCommit(event) {
10056
10068
  if (!event._userId) {
10057
10069
  throw new Error("mark:commit missing _userId (gateway injection)");
10058
10070
  }
10059
10071
  const annotations = event.annotations ?? [];
10072
+ const rid = resourceId(event.resourceId);
10060
10073
  try {
10061
- let persisted = 0;
10074
+ const view = await this.stores.eventStore.viewStorage.get(rid);
10075
+ const present = new Set((view?.annotations.annotations ?? []).map((a) => String(a.id)));
10062
10076
  for (const annotation of annotations) {
10077
+ if (present.has(String(annotation.id))) continue;
10063
10078
  await this.stores.eventStore.appendEvent({
10064
10079
  type: "mark:added",
10065
- resourceId: resourceId(event.resourceId),
10080
+ resourceId: rid,
10066
10081
  userId: userId(event._userId),
10067
10082
  version: 1,
10068
10083
  payload: { annotation }
10069
10084
  });
10070
- persisted++;
10085
+ present.add(String(annotation.id));
10071
10086
  }
10072
10087
  this.logger.debug("Committed annotation batch", {
10073
10088
  correlationId: event.correlationId,
10074
10089
  resourceId: event.resourceId,
10075
- persisted
10090
+ persisted: annotations.length
10076
10091
  });
10077
10092
  this.eventBus.get("mark:commit-ok").next({
10078
10093
  correlationId: event.correlationId,
10079
- response: { persisted, annotationIds: annotations.map((a) => String(a.id)) }
10094
+ // The DURABLE count, which is what the acknowledgement means ("every
10095
+ // annotation named by the command is in the event log"). Not an append
10096
+ // tally: a retry whose annotations are all already present has
10097
+ // succeeded, and must be indistinguishable from the first commit or the
10098
+ // caller would have to interpret a 0 that means "all good".
10099
+ response: { persisted: annotations.length, annotationIds: annotations.map((a) => String(a.id)) }
10080
10100
  });
10081
10101
  } catch (error) {
10082
10102
  this.logger.error("Failed to commit annotation batch", {
@@ -10302,7 +10322,11 @@ var Stower = class {
10302
10322
  jobId: event.jobId,
10303
10323
  jobType: event.jobType,
10304
10324
  ...event.annotationId ? { annotationId: event.annotationId } : {},
10305
- result: event.result
10325
+ result: event.result,
10326
+ // How durability was ESTABLISHED (COMMIT-ACK-FALSE-FAILURE). An
10327
+ // acknowledged batch and one inferred from a probe are different
10328
+ // claims; absent means the question never arose.
10329
+ ...event.durability !== void 0 ? { durability: event.durability } : {}
10306
10330
  }
10307
10331
  });
10308
10332
  }
@@ -10319,7 +10343,19 @@ var Stower = class {
10319
10343
  jobId: event.jobId,
10320
10344
  jobType: event.jobType,
10321
10345
  ...event.annotationId ? { annotationId: event.annotationId } : {},
10322
- error: event.error
10346
+ error: event.error,
10347
+ // The worker's JUDGMENTS, not just its message. Both are computed where
10348
+ // the error is still typed and are unrecoverable here — the only other
10349
+ // witness in the log is `error`, a flattened English string. Spread
10350
+ // conditionally: absent `failureClass` means UNRECOGNISED, a different
10351
+ // claim from 'transient', and defaulting either would write a judgment
10352
+ // nobody made into a log nobody can rewrite.
10353
+ ...event.failureClass !== void 0 ? { failureClass: event.failureClass } : {},
10354
+ ...event.willRetry !== void 0 ? { willRetry: event.willRetry } : {},
10355
+ // How durability was ESTABLISHED (COMMIT-ACK-FALSE-FAILURE). An
10356
+ // acknowledged batch and one inferred from a probe are different
10357
+ // claims; absent means the question never arose.
10358
+ ...event.durability !== void 0 ? { durability: event.durability } : {}
10323
10359
  }
10324
10360
  });
10325
10361
  }
@@ -12511,17 +12547,8 @@ async function authenticate() {
12511
12547
  async function main() {
12512
12548
  const { initObservabilityNode } = await import('@semiont/observability/node');
12513
12549
  initObservabilityNode({ serviceName: "semiont-archivist" });
12514
- const { registerRestartCountProvider } = await import('@semiont/observability');
12515
- const supervisorEvents = "/semiont-state/archivist-supervisor/events.log";
12516
- registerRestartCountProvider(async () => {
12517
- try {
12518
- const { readFile } = await import('fs/promises');
12519
- const log = await readFile(supervisorEvents, "utf-8");
12520
- return Math.max(0, log.split("\n").filter((l) => l.includes("starting archivist")).length - 1);
12521
- } catch {
12522
- return 0;
12523
- }
12524
- });
12550
+ const { registerSupervisorRestartCount } = await import('@semiont/observability/node');
12551
+ registerSupervisorRestartCount();
12525
12552
  logger.info("Authenticating", { baseUrl });
12526
12553
  const tokenSubject = new import_rxjs6.BehaviorSubject(accessToken(await authenticate()));
12527
12554
  logger.info("Authenticated");