@semiont/jobs 0.5.30 → 0.5.32

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.
@@ -1,5 +1,5 @@
1
- import { replyChannelsFor, createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, isObject, isString, isNumber, busRequest, isArray, resourceId, getPrimaryMediaType, isGenerationJobParams, capabilitiesOf, assembleAnnotation, findClaimSpan, textExtractionOf, GENERATABLE_MEDIA_TYPES, locate, createFragmentSelector, deriveViews, estimateTokens, chunkText, reconcileSelector, getLocaleEnglishName } from '@semiont/core';
2
- import { archivistContentReads, extractPdfTextLayer, EXTRACTORS, calculateChecksum, withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';
1
+ import { replyChannelsFor, createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, isObject, isString, isNumber, busRequest, isArray, resourceId, getPrimaryMediaType, isGenerationJobParams, capabilitiesOf, assembleAnnotation, findClaimSpan, textSourceOf, yieldsGeometryOf, decodeRepresentation, GENERATABLE_MEDIA_TYPES, locate, createFragmentSelector, deriveViews, estimateTokens, chunkText, reconcileSelector, getLocaleEnglishName } from '@semiont/core';
2
+ import { archivistContentReads, extractPdfTextLayer, withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';
3
3
  import { withSpan, SpanKind, recordJobOutcome, recordAnchorOutcome, recordDetectionCall } from '@semiont/observability';
4
4
  import { createInferenceClient, StructuredReadError } from '@semiont/inference';
5
5
  import { execFileSync } from 'child_process';
@@ -11216,28 +11216,43 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger2
11216
11216
  }
11217
11217
 
11218
11218
  // src/workers/detection/prepare-detection.ts
11219
- async function prepareDetection(mediaType, content, resourceId, userId, generator, store) {
11220
- const extractor = EXTRACTORS[textExtractionOf(mediaType)];
11221
- if (!extractor) return { declined: "no-extractor" };
11222
- const { data } = await content.getBinary(resourceId);
11223
- const bytes = Buffer.from(data);
11224
- const extracted = await extractor.extract(bytes, mediaType, {
11225
- key: calculateChecksum(bytes),
11226
- store
11227
- });
11228
- if (extracted.kind === "declined") return extracted;
11229
- if (!extracted.text.trim()) return { declined: "empty" };
11230
- const items = extracted.items;
11231
- if (items && items.length > 0) {
11232
- const anchored = { text: extracted.text, items };
11233
- return {
11234
- text: extracted.text,
11235
- buildAnnotation: (motivation, match, body) => buildPdfAnnotation(anchored, resourceId, userId, generator, motivation, match, body)
11236
- };
11219
+ async function prepareDetection(mediaType, content, resourceId, userId, generator, consult) {
11220
+ if (textSourceOf(mediaType) === "none") return { declined: "no-extractor" };
11221
+ if (yieldsGeometryOf(mediaType)) {
11222
+ const answer = await consult(resourceId);
11223
+ switch (answer.kind) {
11224
+ case "extracted": {
11225
+ if (!answer.text.trim()) return { declined: "empty" };
11226
+ const anchored = { text: answer.text, items: answer.items ?? [] };
11227
+ return {
11228
+ text: answer.text,
11229
+ buildAnnotation: (motivation, match, body) => buildPdfAnnotation(anchored, resourceId, userId, generator, motivation, match, body)
11230
+ };
11231
+ }
11232
+ // The Smelter's own decline (encrypted, corrupt) — passed through by name.
11233
+ case "declined":
11234
+ return { declined: answer.declined };
11235
+ // Named absences: `not-yet` retries, the other two are terminal.
11236
+ case "not-yet":
11237
+ return { declined: "not-yet" };
11238
+ case "no-map":
11239
+ return { declined: "no-map" };
11240
+ case "unknown":
11241
+ return { declined: "unknown" };
11242
+ default: {
11243
+ const unhandled = answer;
11244
+ throw new DeterministicJobError(
11245
+ `Unhandled anchored-text answer for resource ${resourceId}: ${JSON.stringify(unhandled)}`
11246
+ );
11247
+ }
11248
+ }
11237
11249
  }
11250
+ const { data } = await content.getBinary(resourceId);
11251
+ const text = decodeRepresentation(Buffer.from(data), mediaType);
11252
+ if (!text.trim()) return { declined: "empty" };
11238
11253
  return {
11239
- text: extracted.text,
11240
- buildAnnotation: (motivation, match, body) => buildTextAnnotation(extracted.text, resourceId, userId, generator, motivation, match, body)
11254
+ text,
11255
+ buildAnnotation: (motivation, match, body) => buildTextAnnotation(text, resourceId, userId, generator, motivation, match, body)
11241
11256
  };
11242
11257
  }
11243
11258
  function referenceIdOf(job) {
@@ -11372,13 +11387,19 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
11372
11387
  const mediaType = getPrimaryMediaType(descriptor);
11373
11388
  const source = await withSpan(
11374
11389
  "detection:prepare",
11375
- () => prepareDetection(mediaType ?? "", config.contentReads, resourceId$1, userId, generator, config.anchoredTextStore),
11390
+ () => prepareDetection(mediaType ?? "", config.contentReads, resourceId$1, userId, generator, (rid) => session.client.browse.resourceAnchoredText(rid)),
11376
11391
  { attrs: { "resource.id": resourceId$1, "media.type": mediaType ?? "unknown" } }
11377
11392
  );
11378
11393
  if ("declined" in source) {
11394
+ if (source.declined === "not-yet") {
11395
+ throw new Error(`Anchored text not yet derived for resource ${resourceId$1} \u2014 Smelter has not settled; retrying`);
11396
+ }
11379
11397
  if (source.declined === "no-extractor") {
11380
11398
  throw new DeterministicJobError(`Cannot run ${jobType} on resource ${resourceId$1}: media type '${mediaType ?? "unknown"}' has no extractable text to analyze`);
11381
11399
  }
11400
+ if (source.declined === "no-map" || source.declined === "unknown") {
11401
+ throw new DeterministicJobError(`Cannot run ${jobType} on resource ${resourceId$1}: anchored-text consult returned '${source.declined}'`);
11402
+ }
11382
11403
  await emitEvent(session, "job:complete", {
11383
11404
  ...lifecycleBase,
11384
11405
  result: {
@@ -11611,29 +11632,6 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
11611
11632
  }
11612
11633
  }
11613
11634
 
11614
- // src/anchored-text-over-bus.ts
11615
- function anchoredTextOverBus(client, logger2) {
11616
- return {
11617
- async read(key) {
11618
- try {
11619
- return await client.browse.anchoredTextByChecksum(key);
11620
- } catch (error) {
11621
- logger2?.debug("Anchored-text consult failed \u2014 treating as miss", {
11622
- key,
11623
- reason: error instanceof Error ? error.message : String(error)
11624
- });
11625
- return null;
11626
- }
11627
- },
11628
- async write(key) {
11629
- logger2?.debug("Anchored-text write skipped \u2014 the Smelter is the sole writer", { key });
11630
- },
11631
- async list() {
11632
- return [];
11633
- }
11634
- };
11635
- }
11636
-
11637
11635
  // src/worker-runtime.ts
11638
11636
  var import_rxjs2 = __toESM(require_cjs());
11639
11637
  function buildHealthPayload(workers) {
@@ -11676,7 +11674,12 @@ function startStallWatchdog(opts) {
11676
11674
  var WORKER_AWAITED_OPERATIONS = [
11677
11675
  "job:claim",
11678
11676
  "browse:resource-requested",
11679
- "browse:anchored-text-by-checksum-requested",
11677
+ // Canonical geometry for a geometry-bearing detection: the consult behind
11678
+ // `ConsultAnchoredText` (SMELTER-OWNS-OCR P2). Its omission broke every
11679
+ // PDF detection job at the transport probe
11680
+ // (.plans/WORKER-ANCHORED-TEXT-CHANNEL.md); the census below now fails the
11681
+ // BUILD when this list and the declared awaits drift.
11682
+ "browse:anchored-text-requested",
11680
11683
  // Durability acknowledgement for a unit's annotations (JOB-RESTART-SAFETY
11681
11684
  // P6). The worker AWAITS this one — a unit may not advance until its
11682
11685
  // annotations are in the event log — so its replies must be in the narrow
@@ -11788,11 +11791,10 @@ async function startAgentWorker(opts) {
11788
11791
  jobTypes: group.jobTypes,
11789
11792
  inferenceClient: group.client,
11790
11793
  generator,
11791
- // The extraction seam's cache, consulted over the bus and never written
11792
- // (ANCHORED-TEXT-TO-SMELTER D2): the Smelter owns this store, the
11793
- // Archivist answers the checksum-addressed read, and a worker that
11794
- // misses extracts locally and discards.
11795
- anchoredTextStore: anchoredTextOverBus(client, logger2),
11794
+ // Byte reads for decode-path media only. A geometry-bearing type's text
11795
+ // never comes from bytes here — it is CONSULTED from the Smelter's
11796
+ // canonical anchored text over the bus (SMELTER-OWNS-OCR P2), and this
11797
+ // worker cannot derive even by mistake (READ-VS-EXTRACT P2).
11796
11798
  contentReads: contentReads2,
11797
11799
  logger: logger2
11798
11800
  });