@semiont/make-meaning 0.5.29 → 0.5.30

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/dist/index.d.ts CHANGED
@@ -337,6 +337,25 @@ declare class Stower {
337
337
  private handleYieldUpdate;
338
338
  private handleYieldMv;
339
339
  private handleMarkCreate;
340
+ /**
341
+ * Persist a detection unit's annotations as ONE acknowledged batch, then
342
+ * answer (JOB-RESTART-SAFETY P6).
343
+ *
344
+ * The difference from `mark:create` is the reply, and it is the whole point.
345
+ * `mark:create` is fire-and-forget: the worker's emit resolves when the bus
346
+ * accepts it, which says nothing about the event log, so a down Stower loses
347
+ * a unit silently and a flapping one hangs the worker forever. This answers
348
+ * only after every append has returned, so the worker can gate unit
349
+ * completion — and its checkpoint — on durability.
350
+ *
351
+ * Appends are sequential, not concurrent: the event log is the system of
352
+ * record and a batch that half-lands under concurrency is harder to reason
353
+ * about than one that stops at the first failure. A partial batch is
354
+ * reported as a failure and the worker retries the WHOLE unit, which is safe
355
+ * because ids are deterministic (P3) and the annotation fold is idempotent
356
+ * by id — re-appending what already landed changes nothing.
357
+ */
358
+ private handleMarkCommit;
340
359
  private handleMarkDelete;
341
360
  private handleMarkUpdateBody;
342
361
  private handleMarkArchive;
package/dist/index.js CHANGED
@@ -11335,6 +11335,7 @@ var Stower = class {
11335
11335
  pipe("yield:update", (e) => this.handleYieldUpdate(e)),
11336
11336
  pipe("yield:mv", (e) => this.handleYieldMv(e)),
11337
11337
  pipe("mark:create", (e) => this.handleMarkCreate(e)),
11338
+ pipe("mark:commit", (e) => this.handleMarkCommit(e)),
11338
11339
  pipe("mark:delete", (e) => this.handleMarkDelete(e)),
11339
11340
  pipe("mark:update-body", (e) => this.handleMarkUpdateBody(e)),
11340
11341
  pipe("frame:add-entity-type", (e) => this.handleAddEntityType(e)),
@@ -11560,6 +11561,61 @@ var Stower = class {
11560
11561
  });
11561
11562
  }
11562
11563
  }
11564
+ /**
11565
+ * Persist a detection unit's annotations as ONE acknowledged batch, then
11566
+ * answer (JOB-RESTART-SAFETY P6).
11567
+ *
11568
+ * The difference from `mark:create` is the reply, and it is the whole point.
11569
+ * `mark:create` is fire-and-forget: the worker's emit resolves when the bus
11570
+ * accepts it, which says nothing about the event log, so a down Stower loses
11571
+ * a unit silently and a flapping one hangs the worker forever. This answers
11572
+ * only after every append has returned, so the worker can gate unit
11573
+ * completion — and its checkpoint — on durability.
11574
+ *
11575
+ * Appends are sequential, not concurrent: the event log is the system of
11576
+ * record and a batch that half-lands under concurrency is harder to reason
11577
+ * about than one that stops at the first failure. A partial batch is
11578
+ * reported as a failure and the worker retries the WHOLE unit, which is safe
11579
+ * because ids are deterministic (P3) and the annotation fold is idempotent
11580
+ * by id — re-appending what already landed changes nothing.
11581
+ */
11582
+ async handleMarkCommit(event) {
11583
+ if (!event._userId) {
11584
+ throw new Error("mark:commit missing _userId (gateway injection)");
11585
+ }
11586
+ const annotations = event.annotations ?? [];
11587
+ try {
11588
+ let persisted = 0;
11589
+ for (const annotation of annotations) {
11590
+ await this.stores.eventStore.appendEvent({
11591
+ type: "mark:added",
11592
+ resourceId: resourceId(event.resourceId),
11593
+ userId: userId(event._userId),
11594
+ version: 1,
11595
+ payload: { annotation }
11596
+ });
11597
+ persisted++;
11598
+ }
11599
+ this.logger.debug("Committed annotation batch", {
11600
+ correlationId: event.correlationId,
11601
+ resourceId: event.resourceId,
11602
+ persisted
11603
+ });
11604
+ this.eventBus.get("mark:commit-ok").next({
11605
+ correlationId: event.correlationId,
11606
+ response: { persisted, annotationIds: annotations.map((a) => String(a.id)) }
11607
+ });
11608
+ } catch (error) {
11609
+ this.logger.error("Failed to commit annotation batch", {
11610
+ correlationId: event.correlationId,
11611
+ error: errField(error)
11612
+ });
11613
+ this.eventBus.get("mark:commit-failed").next({
11614
+ correlationId: event.correlationId,
11615
+ message: error instanceof Error ? error.message : String(error)
11616
+ });
11617
+ }
11618
+ }
11563
11619
  async handleMarkDelete(event) {
11564
11620
  if (!event._userId) {
11565
11621
  throw new Error("mark:delete missing _userId (gateway injection)");
@@ -13194,7 +13250,7 @@ function registerJobCommandHandlers(eventBus, jobQueue, state, parentLogger) {
13194
13250
  });
13195
13251
  eventBus.get("job:fail").subscribe(async (event) => {
13196
13252
  try {
13197
- const outcome = await jobQueue.failJob(jobId(event.jobId), event.error);
13253
+ const outcome = await jobQueue.failJob(jobId(event.jobId), event.error, event.completedUnits, event.failureClass);
13198
13254
  if (outcome === "retried") {
13199
13255
  logger.info("Job re-queued for retry", { jobId: event.jobId });
13200
13256
  } else if (outcome === null) {
@@ -13220,16 +13276,45 @@ function registerJobCommandHandlers(eventBus, jobQueue, state, parentLogger) {
13220
13276
  });
13221
13277
  }
13222
13278
  });
13279
+ eventBus.get("job:checkpoint").subscribe(async (event) => {
13280
+ try {
13281
+ await jobQueue.checkpointUnits(jobId(event.jobId), event.completedUnits);
13282
+ } catch (error) {
13283
+ logger.error("Failed to checkpoint job units", {
13284
+ jobId: event.jobId,
13285
+ error: error.message
13286
+ });
13287
+ }
13288
+ });
13223
13289
  eventBus.get("job:cancel-requested").subscribe(async (event) => {
13224
13290
  try {
13225
- const cancelled = await jobQueue.cancelPendingJobs(event.jobType);
13226
- logger.info("Cancel requested", { jobType: event.jobType, cancelled });
13291
+ let cancelled;
13292
+ if (event.jobId) {
13293
+ const target = await jobQueue.getJob(jobId(event.jobId));
13294
+ if (!target) {
13295
+ cancelled = 0;
13296
+ } else if (target.status === "pending") {
13297
+ cancelled = await jobQueue.cancelJob(jobId(event.jobId)) ? 1 : 0;
13298
+ } else if (target.status === "running") {
13299
+ logger.info("Cancel of running job delegated to its worker", { jobId: event.jobId });
13300
+ cancelled = 1;
13301
+ } else {
13302
+ cancelled = 0;
13303
+ }
13304
+ logger.info("Cancel requested", { jobId: event.jobId, cancelled });
13305
+ } else if (event.jobType) {
13306
+ cancelled = await jobQueue.cancelPendingJobs(event.jobType);
13307
+ logger.info("Cancel requested", { jobType: event.jobType, cancelled });
13308
+ } else {
13309
+ cancelled = 0;
13310
+ }
13227
13311
  eventBus.get("job:cancel-ok").next({
13228
13312
  correlationId: event.correlationId,
13229
13313
  response: { cancelled }
13230
13314
  });
13231
13315
  } catch (error) {
13232
- logger.error("Failed to cancel pending jobs", {
13316
+ logger.error("Failed to cancel jobs", {
13317
+ jobId: event.jobId,
13233
13318
  jobType: event.jobType,
13234
13319
  error: error.message
13235
13320
  });
@@ -13239,6 +13324,17 @@ function registerJobCommandHandlers(eventBus, jobQueue, state, parentLogger) {
13239
13324
  });
13240
13325
  }
13241
13326
  });
13327
+ eventBus.get("job:cancel").subscribe(async (event) => {
13328
+ try {
13329
+ await jobQueue.cancelJob(jobId(event.jobId));
13330
+ logger.info("Job cancelled by its worker", { jobId: event.jobId });
13331
+ } catch (error) {
13332
+ logger.error("Failed to cancel job", {
13333
+ jobId: event.jobId,
13334
+ error: error.message
13335
+ });
13336
+ }
13337
+ });
13242
13338
  }
13243
13339
 
13244
13340
  // src/handlers/index.ts