@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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/will-retry.ts","../src/fs-job-queue.ts","../src/types.ts","../src/workers/inference-call.ts","../src/failure-class.ts","../src/workers/detection/detection-chunking.ts","../src/workers/detection/motivation-prompts.ts","../src/workers/detection/anchor-audit.ts","../src/workers/detection/motivation-parsers.ts","../src/workers/annotation-detection.ts","../src/workers/detection/entity-extractor.ts","../src/workers/generation/resource-generation.ts","../src/workers/generation/typst-compiler.ts","../src/workers/generation/citation-resolver.ts","../src/workers/detection/bounded-concurrency.ts","../src/processors.ts","../src/worker-runtime.ts"],"names":["fs","toJobId","StructuredReadError","isObject","isString","chunkText","getLocaleEnglishName","estimateTokens","response","join","reconcileSelector","generated","citations"],"mappings":"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","file":"index.js","sourcesContent":["import type { JobMetadata } from './types';\n\n/**\n * Will this failure be re-queued for another attempt?\n *\n * ONE decision site, deliberately (JOB-RESTART-SAFETY P5). Two places need\n * the answer and they must never disagree:\n *\n * - `FsJobQueue.failJob` acts on it — re-queue or write the terminal record;\n * - the worker reports it on `job:fail` as `willRetry`, so a client's\n * job-watch stream knows whether the failure it just saw is the end.\n *\n * A second copy of this predicate would be the classic N-places-decide-one-\n * thing bug: the client would end a stream the queue then revived, or hold a\n * stream open on a job nobody will run again. The worker reads the budget off\n * the record it claimed, and `retryCount` only changes inside `failJob`, so\n * the two evaluations see the same numbers.\n *\n * A known-deterministic failure skips the budget outright — the same request\n * cannot succeed on a second attempt, so a retry is guaranteed waste at full\n * price. Unclassified failures are treated as transient.\n */\nexport function willRetryAfter(\n metadata: Pick<JobMetadata, 'retryCount' | 'maxRetries'>,\n failureClass?: 'transient' | 'deterministic',\n): boolean {\n return failureClass !== 'deterministic' && metadata.retryCount < metadata.maxRetries;\n}\n","/**\n * Job Queue Manager\n *\n * Filesystem-based job queue with atomic operations.\n * Jobs are stored in directories by status; status transitions are\n * atomic delete + write across directories.\n */\n\nimport { promises as fs } from 'fs';\nimport * as path from 'path';\nimport type { AnyJob, JobStatus, JobQueryFilters, CancelledJob, CompleteJob, FailedJob, PendingJob, RunningJob } from './types';\nimport type { SemiontState } from '@semiont/core/node';\nimport { jobId as toJobId, type JobId, type Logger, type EventBus } from '@semiont/core';\nimport type { JobQueue } from './job-queue-interface';\nimport { willRetryAfter } from './will-retry';\n\n/**\n * How often pending jobs are re-announced on `job:queued` and stale\n * running jobs are checked for recovery.\n *\n * The announcement in `createJob` only reaches workers that are\n * connected and idle at that moment. Re-announcing every pending job\n * on an interval restores catch-up for everything that announcement\n * misses: all eligible workers busy, a worker offline or mid-SSE-\n * reconnect, or a gateway restart with a pending backlog. Claim\n * arbitration (the `job:claim` handler refuses non-pending jobs)\n * makes duplicate announcements harmless.\n */\nconst REANNOUNCE_INTERVAL_MS = 30_000;\n\n/**\n * A running job whose file hasn't been touched for this long is\n * presumed orphaned by a dead worker and fed through the retry-or-fail\n * path. Progress writes (`recordProgress`) refresh the file's mtime,\n * so this is a heartbeat timeout, not a job-duration limit — but a\n * worker that emits no progress for the whole window will be falsely\n * recovered, so it stays deliberately generous.\n */\nconst STALE_RUNNING_MS = 30 * 60_000;\n\n/** Minimum spacing between progress writes per job — workers can be chatty. */\nconst PROGRESS_WRITE_MIN_INTERVAL_MS = 5_000;\n\n/** Terminal jobs (complete/failed/cancelled) are pruned after this long. */\nconst RETENTION_HOURS = 24;\n\n/** How often the retention pruning runs. */\nconst CLEANUP_INTERVAL_MS = 3_600_000;\n\nexport class FsJobQueue implements JobQueue {\n private jobsDir: string;\n private logger: Logger;\n private reannounceTimer: ReturnType<typeof setInterval> | null = null;\n private cleanupTimer: ReturnType<typeof setInterval> | null = null;\n /** Per-job timestamp of the last progress write, for throttling. */\n private lastProgressWrite = new Map<string, number>();\n\n constructor(\n /** `SemiontState` and not `SemiontProject`: the queue reads ONE path,\n * and the gateway that owns it mounts no KB tree (SINGLE-KB-MOUNT P5). */\n state: SemiontState,\n logger: Logger,\n private eventBus?: EventBus\n ) {\n this.jobsDir = state.jobsDir;\n this.logger = logger;\n }\n\n /**\n * Initialize job queue directories, announce any pending backlog,\n * and start the re-announce interval. Idempotent.\n */\n async initialize(): Promise<void> {\n const statuses: JobStatus[] = ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const dir = path.join(this.jobsDir, status);\n await fs.mkdir(dir, { recursive: true });\n }\n\n if (this.eventBus && !this.reannounceTimer) {\n // Jobs left pending across a restart are announced right away…\n await this.announcePendingJobs();\n // …and anything that misses an announcement is retried here, along\n // with recovery of jobs orphaned by a dead worker.\n this.reannounceTimer = setInterval(() => {\n this.announcePendingJobs().catch((error) => {\n this.logger.warn('Pending-job re-announce failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n this.recoverStaleRunningJobs().catch((error) => {\n this.logger.warn('Stale-running recovery failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }, REANNOUNCE_INTERVAL_MS);\n // Don't let the interval keep the process alive on shutdown.\n this.reannounceTimer.unref?.();\n }\n\n if (!this.cleanupTimer) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupOldJobs(RETENTION_HOURS).catch((error) => {\n this.logger.warn('Job retention cleanup failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }, CLEANUP_INTERVAL_MS);\n this.cleanupTimer.unref?.();\n }\n\n this.logger.info('Job queue initialized');\n }\n\n /**\n * Stop the re-announce and retention intervals\n */\n destroy(): void {\n if (this.reannounceTimer) {\n clearInterval(this.reannounceTimer);\n this.reannounceTimer = null;\n }\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = null;\n }\n }\n\n /**\n * Emit `job:queued` for a pending job, if an EventBus is wired and\n * the job carries a `resourceId` (every current job type does).\n */\n private announce(job: AnyJob): void {\n if (this.eventBus && 'params' in job && 'resourceId' in job.params) {\n this.eventBus.get('job:queued').next({\n jobId: job.metadata.id,\n jobType: job.metadata.type,\n resourceId: job.params.resourceId,\n userId: job.metadata.userId,\n });\n }\n }\n\n /**\n * Announce every job currently in `pending/`. Files that vanish or\n * fail to parse mid-scan (claimed, cancelled, partially written)\n * are skipped — they're either gone for a good reason or picked up\n * on the next tick.\n */\n private async announcePendingJobs(): Promise<void> {\n const pendingDir = path.join(this.jobsDir, 'pending');\n let files: string[];\n try {\n files = await fs.readdir(pendingDir);\n } catch {\n return;\n }\n files.sort();\n for (const file of files) {\n try {\n const content = await fs.readFile(path.join(pendingDir, file), 'utf-8');\n this.announce(JSON.parse(content) as AnyJob);\n } catch {\n // Skip unreadable files\n }\n }\n }\n\n /**\n * Create a new job\n */\n async createJob(job: AnyJob): Promise<void> {\n const jobPath = this.getJobPath(job.metadata.id, job.status);\n await fs.writeFile(jobPath, JSON.stringify(job, null, 2), 'utf-8');\n this.logger.info('Job created', { jobId: job.metadata.id, status: job.status });\n\n if (job.status === 'pending') {\n this.announce(job);\n }\n }\n\n /**\n * Get a job by ID (searches all status directories)\n */\n async getJob(jobId: JobId): Promise<AnyJob | null> {\n const statuses: JobStatus[] = ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const jobPath = this.getJobPath(jobId, status);\n try {\n const content = await fs.readFile(jobPath, 'utf-8');\n return JSON.parse(content) as AnyJob;\n } catch (error) {\n // File doesn't exist in this status directory, try next\n continue;\n }\n }\n\n return null;\n }\n\n /**\n * Update a job (atomic: delete old, write new)\n */\n async updateJob(job: AnyJob, oldStatus?: JobStatus): Promise<void> {\n // If oldStatus provided, delete from old location\n if (oldStatus && oldStatus !== job.status) {\n const oldPath = this.getJobPath(job.metadata.id, oldStatus);\n try {\n await fs.unlink(oldPath);\n } catch (error) {\n // Ignore if file doesn't exist\n }\n }\n\n // Write to new location\n const newPath = this.getJobPath(job.metadata.id, job.status);\n await fs.writeFile(newPath, JSON.stringify(job, null, 2), 'utf-8');\n\n if (oldStatus && oldStatus !== job.status) {\n this.logger.info('Job moved', { jobId: job.metadata.id, oldStatus, newStatus: job.status });\n // Re-entering pending (e.g. a retry) is a fresh announcement.\n if (job.status === 'pending') {\n this.announce(job);\n }\n } else {\n this.logger.info('Job updated', { jobId: job.metadata.id, status: job.status });\n }\n }\n\n /**\n * Move a running job to `complete`. Returns false (and changes\n * nothing) if the job is missing or not running — which also makes\n * duplicate `job:complete` events harmless.\n */\n async completeJob(jobId: JobId, result: Record<string, unknown>): Promise<boolean> {\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n return false;\n }\n\n this.lastProgressWrite.delete(jobId);\n const completed: CompleteJob<any, any> = {\n status: 'complete',\n metadata: job.metadata,\n params: job.params,\n startedAt: job.startedAt,\n completedAt: new Date().toISOString(),\n result,\n };\n await this.updateJob(completed, 'running');\n return true;\n }\n\n /**\n * Retry-or-fail a running job. While `retryCount < maxRetries` the\n * job goes back to `pending` with the count bumped (and is\n * re-announced); after that it lands in `failed` with the error.\n * Returns null (and changes nothing) if the job isn't running.\n */\n async failJob(jobId: JobId, error: string, completedUnits?: string[], failureClass?: 'transient' | 'deterministic'): Promise<'retried' | 'failed' | null> {\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n return null;\n }\n\n this.lastProgressWrite.delete(jobId);\n\n // Checkpointed resume (ABANDONED-INFERENCE P2): union this attempt's\n // completed units with earlier attempts' — attempt 3 must keep what\n // attempt 1 finished. Stored in metadata so the spread below (and in\n // every later rebuild) carries it forward; the terminal failed record\n // keeps it too, so the waste of a discarded run stays visible (D3).\n const checkpoint = [...new Set([...(job.metadata.completedUnits ?? []), ...(completedUnits ?? [])])];\n const metadata = checkpoint.length > 0\n ? { ...job.metadata, completedUnits: checkpoint }\n : job.metadata;\n\n // A known-deterministic failure skips the budget outright: the same\n // request cannot succeed on a second attempt, so a retry is guaranteed\n // waste at full price (ABANDONED-INFERENCE P3, A4). Unclassified and\n // transient failures retry as before.\n if (willRetryAfter(job.metadata, failureClass)) {\n const retried: PendingJob<any> = {\n status: 'pending',\n metadata: { ...metadata, retryCount: job.metadata.retryCount + 1 },\n params: job.params,\n };\n await this.updateJob(retried, 'running');\n return 'retried';\n }\n\n if (failureClass === 'deterministic' && job.metadata.retryCount < job.metadata.maxRetries) {\n this.logger.info('Job failed without retry — deterministic failure', { jobId, error });\n }\n\n const failed: FailedJob<any> = {\n status: 'failed',\n metadata,\n params: job.params,\n startedAt: job.startedAt,\n completedAt: new Date().toISOString(),\n error,\n };\n await this.updateJob(failed, 'running');\n return 'failed';\n }\n\n /**\n * Persist a running job's completed-unit checkpoint at unit completion\n * (JOB-RESTART-SAFETY P2). `failJob` writes this checkpoint on a clean\n * failure, but a worker that dies without emitting `job:fail` would lose\n * its finished units — so the janitor's recovery (which re-queues with\n * whatever `metadata.completedUnits` the running file holds) would redo\n * them. Writing it HERE, as each unit lands, makes a crash lose at most\n * the in-flight unit. Unioned with any existing checkpoint, unthrottled\n * (a unit completion is rare and must never be dropped), a no-op for\n * non-running jobs. Written directly, like `recordProgress`, so it also\n * refreshes the mtime heartbeat.\n */\n async checkpointUnits(jobId: JobId, completedUnits: string[]): Promise<void> {\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n return;\n }\n const merged = [...new Set([...(job.metadata.completedUnits ?? []), ...completedUnits])];\n const updated: RunningJob<any, any> = {\n ...job,\n metadata: { ...job.metadata, completedUnits: merged },\n };\n await fs.writeFile(this.getJobPath(jobId, 'running'), JSON.stringify(updated, null, 2), 'utf-8');\n }\n\n /**\n * Write progress into a running job's file. Throttled per job, and\n * a no-op for jobs that aren't running. Beyond surfacing live\n * progress to `job:status-requested`, each write refreshes the\n * file's mtime — the heartbeat `recoverStaleRunningJobs` watches.\n */\n async recordProgress(jobId: JobId, progress: Record<string, unknown>): Promise<void> {\n const now = Date.now();\n const lastWrite = this.lastProgressWrite.get(jobId) ?? 0;\n if (now - lastWrite < PROGRESS_WRITE_MIN_INTERVAL_MS) {\n return;\n }\n this.lastProgressWrite.set(jobId, now);\n\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n this.lastProgressWrite.delete(jobId);\n return;\n }\n\n // Written directly (not via updateJob) so chatty progress doesn't\n // flood the info log.\n const updated: RunningJob<any, any> = { ...job, progress };\n await fs.writeFile(this.getJobPath(jobId, 'running'), JSON.stringify(updated, null, 2), 'utf-8');\n }\n\n /**\n * List jobs with filters\n */\n async listJobs(filters: JobQueryFilters = {}): Promise<AnyJob[]> {\n const jobs: AnyJob[] = [];\n\n // Determine which status directories to scan\n const statuses: JobStatus[] = filters.status\n ? [filters.status]\n : ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const statusDir = path.join(this.jobsDir, status);\n\n try {\n const files = await fs.readdir(statusDir);\n\n for (const file of files) {\n const jobPath = path.join(statusDir, file);\n const content = await fs.readFile(jobPath, 'utf-8');\n const job = JSON.parse(content) as AnyJob;\n\n // Apply filters\n if (filters.type && job.metadata.type !== filters.type) continue;\n if (filters.userId && job.metadata.userId !== filters.userId) continue;\n\n jobs.push(job);\n }\n } catch (error) {\n // Directory might not exist yet\n continue;\n }\n }\n\n // Sort by created descending (newest first)\n jobs.sort((a, b) => new Date(b.metadata.created).getTime() - new Date(a.metadata.created).getTime());\n\n // Apply pagination\n const offset = filters.offset || 0;\n const limit = filters.limit || 100;\n\n return jobs.slice(offset, offset + limit);\n }\n\n /**\n * Cancel a job\n */\n async cancelJob(jobId: JobId): Promise<boolean> {\n const job = await this.getJob(jobId);\n\n if (!job) {\n return false;\n }\n\n // Can only cancel pending or running jobs\n if (job.status !== 'pending' && job.status !== 'running') {\n return false;\n }\n\n const oldStatus = job.status;\n\n // Create cancelled job with proper structure\n const cancelledJob: CancelledJob<any> = {\n status: 'cancelled',\n metadata: job.metadata,\n params: job.status === 'pending' ? job.params : job.params,\n startedAt: job.status === 'running' ? job.startedAt : undefined,\n completedAt: new Date().toISOString(),\n };\n\n await this.updateJob(cancelledJob, oldStatus);\n return true;\n }\n\n /**\n * Cancel all pending jobs in a category — the granularity of the\n * `job:cancel-requested` UI signal. Running jobs are left to finish:\n * interrupting a worker mid-inference would need a worker-side kill\n * channel that doesn't exist.\n */\n async cancelPendingJobs(category: 'annotation' | 'generation'): Promise<number> {\n const matches = category === 'generation'\n ? (type: string) => type === 'generation'\n : (type: string) => type.endsWith('-annotation');\n\n const pending = await this.listJobs({ status: 'pending', limit: Number.MAX_SAFE_INTEGER });\n let cancelled = 0;\n for (const job of pending) {\n if (!matches(job.metadata.type)) continue;\n if (await this.cancelJob(job.metadata.id)) {\n cancelled++;\n }\n }\n\n if (cancelled > 0) {\n this.logger.info('Cancelled pending jobs', { category, cancelled });\n }\n return cancelled;\n }\n\n /**\n * Recover running jobs orphaned by a dead worker: any `running/`\n * file whose mtime is older than the stale window is fed through\n * the same retry-or-fail path as `job:fail`. Progress writes\n * refresh the mtime, so a live worker is never recovered out from\n * under itself as long as it reports within the window.\n */\n async recoverStaleRunningJobs(): Promise<number> {\n const runningDir = path.join(this.jobsDir, 'running');\n let files: string[];\n try {\n files = await fs.readdir(runningDir);\n } catch {\n return 0;\n }\n\n const now = Date.now();\n let recovered = 0;\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n try {\n const stat = await fs.stat(path.join(runningDir, file));\n if (now - stat.mtimeMs < STALE_RUNNING_MS) continue;\n\n const staleId = toJobId(file.slice(0, -'.json'.length));\n const outcome = await this.failJob(\n staleId,\n `worker presumed dead — no progress within ${STALE_RUNNING_MS / 60_000} minutes`,\n );\n if (outcome) {\n this.logger.warn('Recovered stale running job', { jobId: staleId, outcome });\n recovered++;\n }\n } catch {\n // File vanished mid-scan (job finished) — nothing to recover\n }\n }\n return recovered;\n }\n\n /**\n * Clean up old completed/failed jobs (older than retention period)\n */\n async cleanupOldJobs(retentionHours: number = 24): Promise<number> {\n const cutoffTime = Date.now() - (retentionHours * 60 * 60 * 1000);\n let deletedCount = 0;\n\n const cleanupStatuses: JobStatus[] = ['complete', 'failed', 'cancelled'];\n\n for (const status of cleanupStatuses) {\n const statusDir = path.join(this.jobsDir, status);\n\n try {\n const files = await fs.readdir(statusDir);\n\n for (const file of files) {\n const jobPath = path.join(statusDir, file);\n const content = await fs.readFile(jobPath, 'utf-8');\n const job = JSON.parse(content) as AnyJob;\n\n if (job.status === 'complete' || job.status === 'failed' || job.status === 'cancelled') {\n const completedTime = new Date(job.completedAt).getTime();\n\n if (completedTime < cutoffTime) {\n await fs.unlink(jobPath);\n deletedCount++;\n }\n }\n }\n } catch (error) {\n this.logger.error('Error cleaning up jobs', { status, error: error instanceof Error ? error.message : String(error) });\n }\n }\n\n if (deletedCount > 0) {\n this.logger.info('Jobs cleaned up', { deletedCount });\n }\n\n return deletedCount;\n }\n\n /**\n * Get job file path\n */\n private getJobPath(jobId: JobId, status: JobStatus): string {\n return path.join(this.jobsDir, status, `${jobId}.json`);\n }\n\n /**\n * Get statistics about the queue\n */\n async getStats(): Promise<{\n pending: number;\n running: number;\n complete: number;\n failed: number;\n cancelled: number;\n }> {\n const stats = {\n pending: 0,\n running: 0,\n complete: 0,\n failed: 0,\n cancelled: 0\n };\n\n const statuses: JobStatus[] = ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const statusDir = path.join(this.jobsDir, status);\n\n try {\n const files = await fs.readdir(statusDir);\n stats[status] = files.length;\n } catch (error) {\n // Directory might not exist yet\n stats[status] = 0;\n }\n }\n\n return stats;\n }\n}\n","/**\n * Job Queue Type Definitions - Discriminated Union Design\n *\n * Jobs represent async work that can be queued, processed, and monitored.\n * Uses TypeScript discriminated unions to enforce valid state transitions.\n *\n * Design principles:\n * - Each job status has specific valid fields\n * - Type narrowing works automatically via status discriminant\n * - No optional fields that may or may not exist\n * - State machine is explicit and type-safe\n */\n\nimport type { JobId, EntityType, ResourceId, UserId, GenerationJobParams, TagSchema } from '@semiont/core';\n\nexport type JobType = 'reference-annotation' | 'generation' | 'highlight-annotation' | 'assessment-annotation' | 'comment-annotation' | 'tag-annotation';\nexport type JobStatus = 'pending' | 'running' | 'complete' | 'failed' | 'cancelled';\n\n// ============================================================================\n// Core Metadata and Parameters\n// ============================================================================\n\n/**\n * Job metadata - common to all states\n */\nexport interface JobMetadata {\n id: JobId;\n type: JobType;\n userId: UserId;\n /**\n * Audit-only snapshot of the requesting user (with `userEmail` and\n * `userDomain` below), stamped at job creation and persisted in the\n * on-disk job file. No code path reads these back — annotation\n * `creator` attribution is derived from `userId` via `didToAgent()`.\n * Kept intentionally so job files are self-describing to a human.\n */\n userName: string;\n userEmail: string;\n userDomain: string;\n created: string;\n retryCount: number;\n maxRetries: number;\n /**\n * Checkpointed resume (ABANDONED-INFERENCE P2, HD1): the entity-type\n * units whose annotations were fully emitted by earlier failed\n * attempts. Written only by `failJob`, unioned across attempts — and\n * because `failJob` rebuilds the retried record by spreading metadata,\n * the checkpoint survives every subsequent rebuild for free. A retried\n * claim skips these units, so completed work is neither redone nor\n * duplicated.\n */\n completedUnits?: string[];\n}\n\n/**\n * Locale conventions for detection/generation params.\n *\n * Two independent locales flow through these jobs:\n *\n * - `language` — *annotation body* locale. The BCP-47 tag the LLM should\n * write generated body text in (comment text, assessment text, generated\n * resource content, tag category label). Sourced from the user's UI\n * locale. Stamped onto the W3C `TextualBody.language` field.\n *\n * - `sourceLanguage` — *source resource* locale. The BCP-47 tag of the\n * content being analyzed. Sourced from `ResourceDescriptor` (carried as\n * `Representation.language` on the primary representation). Used in\n * prompts so the LLM analyzes non-English source correctly even when\n * the user's UI locale differs.\n *\n * Examples: a German user analyzing an English document → `language='de'`,\n * `sourceLanguage='en'`. An English user detecting entities in a French\n * document → `language='en'` (unused for entity references), `sourceLanguage='fr'`.\n */\n\n/**\n * Detection job parameters\n */\nexport interface DetectionParams {\n resourceId: ResourceId;\n entityTypes: EntityType[];\n includeDescriptiveReferences?: boolean;\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n\n/**\n * Highlight detection job parameters\n */\nexport interface HighlightDetectionParams {\n resourceId: ResourceId;\n instructions?: string;\n density?: number;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n/**\n * Assessment detection job parameters\n */\nexport interface AssessmentDetectionParams {\n resourceId: ResourceId;\n instructions?: string;\n tone?: 'analytical' | 'critical' | 'balanced' | 'constructive';\n density?: number;\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n/**\n * Comment detection job parameters\n */\nexport interface CommentDetectionParams {\n resourceId: ResourceId;\n instructions?: string;\n tone?: 'scholarly' | 'explanatory' | 'conversational' | 'technical';\n density?: number;\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n/**\n * Tag detection job parameters.\n *\n * Carries the *full* `TagSchema` (not just an id). The dispatcher resolves\n * the caller-supplied `schemaId` against the per-KB tag-schema projection\n * at job-creation time and embeds the resolved schema here, keeping the\n * worker independent of the registry.\n */\nexport interface TagDetectionParams {\n resourceId: ResourceId;\n schema: TagSchema;\n categories: string[];\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n// ============================================================================\n// Progress and Result Types\n// ============================================================================\n\n/**\n * Detection job progress\n */\nexport interface DetectionProgress {\n totalEntityTypes: number;\n processedEntityTypes: number;\n entitiesFound: number;\n entitiesEmitted: number;\n}\n\n/**\n * Detection job result\n */\nexport interface DetectionResult {\n kind: 'reference-annotation';\n totalFound: number;\n totalEmitted: number;\n errors: number;\n}\n\n/**\n * Generation job progress\n */\nexport interface YieldProgress {\n /** The two real generation transitions — LLM call running, then persisting. */\n stage: 'generating' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Generation job result\n */\nexport interface GenerationResult {\n kind: 'generation';\n resourceId: ResourceId;\n resourceName: string;\n /** True when the model stopped at the maxTokens ceiling — the artifact is cut off, not complete (GENERATE-FROM-RESOURCE D6). */\n truncated: boolean;\n}\n\n/**\n * Highlight detection job progress\n */\nexport interface HighlightDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Highlight detection job result\n */\nexport interface HighlightDetectionResult {\n kind: 'highlight-annotation';\n highlightsFound: number;\n highlightsCreated: number;\n}\n\n/**\n * Assessment detection job progress\n */\nexport interface AssessmentDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Assessment detection job result\n */\nexport interface AssessmentDetectionResult {\n kind: 'assessment-annotation';\n assessmentsFound: number;\n assessmentsCreated: number;\n}\n\n/**\n * Comment detection job progress\n */\nexport interface CommentDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Comment detection job result\n */\nexport interface CommentDetectionResult {\n kind: 'comment-annotation';\n commentsFound: number;\n commentsCreated: number;\n}\n\n/**\n * Tag detection job progress\n */\nexport interface TagDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n currentCategory?: string;\n processedCategories: number;\n totalCategories: number;\n message?: string;\n}\n\n/**\n * Tag detection job result\n */\nexport interface TagDetectionResult {\n kind: 'tag-annotation';\n tagsFound: number;\n tagsCreated: number;\n byCategory: Record<string, number>;\n}\n\n// ============================================================================\n// Generic Job State Types\n// ============================================================================\n\n/**\n * Pending job - just created, waiting to be picked up\n */\nexport interface PendingJob<P> {\n status: 'pending';\n metadata: JobMetadata;\n params: P;\n}\n\n/**\n * Running job - actively being processed\n */\nexport interface RunningJob<P, PG> {\n status: 'running';\n metadata: JobMetadata;\n params: P;\n startedAt: string;\n progress: PG;\n}\n\n/**\n * Complete job - successfully finished\n */\nexport interface CompleteJob<P, R> {\n status: 'complete';\n metadata: JobMetadata;\n params: P;\n startedAt: string;\n completedAt: string;\n result: R;\n}\n\n/**\n * Failed job - encountered an error\n */\nexport interface FailedJob<P> {\n status: 'failed';\n metadata: JobMetadata;\n params: P;\n startedAt?: string;\n completedAt: string;\n error: string;\n}\n\n/**\n * Cancelled job - stopped by user\n */\nexport interface CancelledJob<P> {\n status: 'cancelled';\n metadata: JobMetadata;\n params: P;\n startedAt?: string;\n completedAt: string;\n}\n\n/**\n * Generic job - discriminated union of all states\n */\nexport type Job<P, PG, R> =\n | PendingJob<P>\n | RunningJob<P, PG>\n | CompleteJob<P, R>\n | FailedJob<P>\n | CancelledJob<P>;\n\n// ============================================================================\n// Concrete Job Types\n// ============================================================================\n\nexport type DetectionJob = Job<DetectionParams, DetectionProgress, DetectionResult>;\nexport type GenerationJob = Job<GenerationJobParams, YieldProgress, GenerationResult>;\nexport type HighlightDetectionJob = Job<HighlightDetectionParams, HighlightDetectionProgress, HighlightDetectionResult>;\nexport type AssessmentDetectionJob = Job<AssessmentDetectionParams, AssessmentDetectionProgress, AssessmentDetectionResult>;\nexport type CommentDetectionJob = Job<CommentDetectionParams, CommentDetectionProgress, CommentDetectionResult>;\nexport type TagDetectionJob = Job<TagDetectionParams, TagDetectionProgress, TagDetectionResult>;\n\n/**\n * Discriminated union of all job types\n */\nexport type AnyJob = DetectionJob | GenerationJob | HighlightDetectionJob | AssessmentDetectionJob | CommentDetectionJob | TagDetectionJob;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\nconst JOB_TYPES: ReadonlySet<string> = new Set<JobType>([\n 'reference-annotation',\n 'generation',\n 'highlight-annotation',\n 'assessment-annotation',\n 'comment-annotation',\n 'tag-annotation',\n]);\n\n/**\n * Narrow a job type arriving off the bus, where it is only `string`.\n *\n * Worth checking rather than asserting: `jobType` is a required, enumerated\n * field on every job lifecycle command, so an unrecognized value produces a\n * payload the gateway will reject — better caught at the worker with a clear\n * error than as an opaque emit failure.\n */\nexport function isJobType(value: string): value is JobType {\n return JOB_TYPES.has(value);\n}\n\n/**\n * Narrow bus-delivered job params to the shape a processor expects.\n *\n * Job params cross the bus as `Record<string, unknown>`. Every params type in\n * this file requires exactly one field — `resourceId` — with the rest\n * optional, so that is what gets verified; absent optionals are legitimately\n * absent, not a validation failure. Callers pass the params type for the job\n * they have already branched on.\n */\nexport function asJobParams<T extends { resourceId: ResourceId }>(\n params: Record<string, unknown>,\n): T {\n if (typeof params.resourceId !== 'string') {\n throw new Error('Job params are missing a resourceId');\n }\n return params as unknown as T;\n}\n\n// Generation params are deliberately NOT covered: generation is the one job\n// that does not read a source resource (it mints one), so it requires no\n// `resourceId` and there is nothing to verify.\n\nexport function isPendingJob(job: AnyJob): job is PendingJob<any> {\n return job.status === 'pending';\n}\n\nexport function isRunningJob(job: AnyJob): job is RunningJob<any, any> {\n return job.status === 'running';\n}\n\nexport function isCompleteJob(job: AnyJob): job is CompleteJob<any, any> {\n return job.status === 'complete';\n}\n\nexport function isFailedJob(job: AnyJob): job is FailedJob<any> {\n return job.status === 'failed';\n}\n\nexport function isCancelledJob(job: AnyJob): job is CancelledJob<any> {\n return job.status === 'cancelled';\n}\n\n// ============================================================================\n// Job Query Types\n// ============================================================================\n\nexport interface JobQueryFilters {\n status?: JobStatus;\n type?: JobType;\n userId?: UserId;\n limit?: number;\n offset?: number;\n}\n\n","/**\n * Bounded inference calls — WORKER-LIVENESS.md P2 (G1: prevention).\n *\n * The claim loop's only unbounded await is the model call: bus\n * operations gained transport timeouts in 0.5.6, the inference HTTP\n * request did not. One request that never settles used to wedge the\n * worker forever — the adapter ignores announcements while\n * `isProcessing`, so a single stuck call silenced the whole agent.\n * Bounding the call converts that silent hang into an ordinary job\n * failure that flows through the existing `job:fail` path (and the\n * gateway's retry budget — a timeout is transient-shaped, so retrying\n * is correct) and frees the claim loop.\n *\n * This is a timeout AND a cancellation (ABANDONED-INFERENCE P1): on expiry\n * the bound aborts the underlying request through the `InferenceClient`\n * signal, so the transport — and, on the Anthropic path, the SDK's internal\n * retry loop — is torn down rather than left running as a billed zombie\n * (P0 caught one completing 24–34 minutes after its job was gone). The\n * timeout stays as the last line either way, exactly as before; the abort is\n * the addition, not the replacement (D1). The eventual settlement of the\n * aborted promise is still swallowed so it cannot surface as an unhandled\n * rejection.\n */\n\nimport type { ElementSchema, InferenceClient, InferenceResponse, StructuredResponse } from '@semiont/inference';\nimport type { Logger } from '@semiont/core';\nimport { withSpan } from '@semiont/observability';\n\n/**\n * Generous single-call bound. Slow local models on large prompts run\n * minutes, not tens of minutes; the stall watchdog (P3) sits above\n * this at 15 minutes, and the gateway's dead-worker janitor above\n * that at 30. Fixed by design — no env knob.\n */\nexport const INFERENCE_TIMEOUT_MS = 10 * 60_000;\n\n/**\n * The bound's own rejection, typed so classification (ABANDONED-INFERENCE\n * P3) never string-matches our own error message. A timeout says nothing\n * about the request — it classifies transient.\n */\nexport class InferenceTimeoutError extends Error {\n override readonly name = 'InferenceTimeoutError';\n}\n\n/**\n * How often an in-flight call reports that it is still alive\n * (DETECTION-HEARTBEAT D2).\n *\n * Detection's other liveness signal — the chunk-boundary heartbeat — emits\n * `N − 1` events for `N` chunks, which is ZERO for the single-chunk case that\n * every realistic document falls into (the derived input budget is ~935 K\n * tokens). A 7-minute call then emits nothing at all, and the client's\n * *inter-emission* timeout (`mark-state-unit`, 180 s) kills a perfectly\n * healthy job.\n *\n * Sized against that consumer: 15 s gives ~12 beats of margin inside the\n * 180 s window. Fixed by design, like the bound above — no env knob.\n */\nexport const INFERENCE_HEARTBEAT_MS = 15_000;\n\n/**\n * Called while a provider call is still in flight. Liveness only — the\n * caller repeats its current stage rather than inventing an advancing\n * percentage (D3): nothing here knows how far a single model call has got.\n */\nexport type InferenceHeartbeat = () => void;\n\n/**\n * One span per provider call. Before this, a 411-second detection job was a\n * SINGLE span with no children on a fully instrumented stack — it was not\n * possible to tell extraction from inference from telemetry, which is what\n * made the sibling silent-empty bug expensive to find. Attributes stay to\n * what is known before the answer arrives; token counts are recorded by\n * `recordInferenceUsage` in the client.\n */\nfunction spanned<T>(client: InferenceClient, kind: string, maxTokens: number, work: () => Promise<T>): Promise<T> {\n return withSpan(`inference:${kind}`, work, {\n attrs: {\n 'inference.provider': client.type,\n 'inference.model': client.modelId,\n 'inference.max_tokens': maxTokens,\n },\n });\n}\n\nasync function withTimeout<T>(\n work: (signal: AbortSignal) => Promise<T>,\n meta: { provider: string; model: string; label: string },\n onHeartbeat?: InferenceHeartbeat,\n logger?: Logger,\n): Promise<T> {\n const controller = new AbortController();\n let timer!: ReturnType<typeof setTimeout>;\n const timedOut = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n // True cancellation: tear the request down at the transport so it\n // cannot keep running (and billing) against a job that no longer\n // exists — and name the abort in the log, because an invisible\n // abandonment is what let a zombie burn 24+ minutes unrecorded (D3).\n logger?.warn('Aborting in-flight inference call at the timeout bound', {\n provider: meta.provider,\n model: meta.model,\n label: meta.label,\n boundMs: INFERENCE_TIMEOUT_MS,\n });\n controller.abort();\n reject(new InferenceTimeoutError(\n `Inference call timed out after ${INFERENCE_TIMEOUT_MS / 60_000} minutes (${meta.label}) — failing the job to keep the claim loop live`,\n ));\n }, INFERENCE_TIMEOUT_MS);\n timer.unref?.();\n });\n\n // One timer at one site covers every provider call, present and future —\n // putting it in the detection loops instead would re-couple liveness to\n // detection's own structure, which is the coupling this exists to undo.\n let heartbeat: ReturnType<typeof setInterval> | undefined;\n if (onHeartbeat) {\n heartbeat = setInterval(() => {\n try {\n onHeartbeat();\n } catch {\n // A failing progress emit must never take down the inference call\n // it is merely reporting on.\n }\n }, INFERENCE_HEARTBEAT_MS);\n heartbeat.unref?.();\n }\n\n const pending = work(controller.signal);\n try {\n return await Promise.race([pending, timedOut]);\n } catch (err) {\n // The aborted call settles promptly now (AbortError from the transport)\n // rather than minutes later — but its rejection still lands after the\n // race is lost, so it is still swallowed here to keep it from surfacing\n // as an unhandled one and killing the process.\n pending.catch(() => {});\n throw err;\n } finally {\n clearTimeout(timer);\n // Cleared with the timeout, in the same finally: a leaked interval would\n // beat forever on a completed job.\n if (heartbeat) clearInterval(heartbeat);\n }\n}\n\nexport function boundedGenerateWithMetadata(\n client: InferenceClient,\n prompt: string,\n maxTokens: number,\n temperature: number,\n onHeartbeat?: InferenceHeartbeat,\n logger?: Logger,\n): Promise<InferenceResponse> {\n return spanned(client, 'text', maxTokens, () => withTimeout(\n (signal) => client.generateTextWithMetadata(prompt, maxTokens, temperature, signal),\n { provider: client.type, model: client.modelId, label: `${client.type}:${client.modelId}` },\n onHeartbeat,\n logger,\n ));\n}\n\nexport function boundedGenerateStructured<T>(\n client: InferenceClient,\n prompt: string,\n maxTokens: number,\n temperature: number,\n elementSchema: ElementSchema,\n onHeartbeat?: InferenceHeartbeat,\n logger?: Logger,\n): Promise<StructuredResponse<T>> {\n return spanned(client, 'structured', maxTokens, () => withTimeout(\n (signal) => client.generateStructured<T>(prompt, maxTokens, temperature, elementSchema, signal),\n { provider: client.type, model: client.modelId, label: `${client.type}:${client.modelId}` },\n onHeartbeat,\n logger,\n ));\n}\n","/**\n * Failure classification — ABANDONED-INFERENCE P3 (A4, HD2).\n *\n * A retried deterministic failure always costs exactly double: the second\n * attempt of the same request cannot succeed. HD2's ruling is one-sided —\n * only KNOWN-deterministic failures skip the retry budget; everything\n * unrecognized stays retryable (`undefined`), because mis-classifying a\n * transient failure as deterministic silently halves reliability, while the\n * reverse merely costs one wasted attempt (today's behavior).\n *\n * Classification happens HERE, in the worker, where errors are still typed —\n * at the gateway's `job:fail` handler the failure is already a flattened\n * string, and message-regex classification is the drift this module exists\n * to avoid. The class rides the `job:fail` payload; `failJob` consumes it.\n */\n\nimport { isNumber, isObject, isString } from '@semiont/core';\nimport { StructuredReadError } from '@semiont/inference';\nimport { InferenceTimeoutError } from './workers/inference-call';\n\nexport type FailureClass = 'transient' | 'deterministic';\n\n/**\n * Marker for failures WE know cannot succeed on a second identical attempt —\n * thrown at the sites that judge the work itself rather than the transport:\n * response truncation despite the derived budget, a media type with nothing\n * to extract. Throw it instead of `Error` wherever that knowledge exists.\n */\nexport class DeterministicJobError extends Error {\n // Typed string, not the literal: subclasses (YieldCollapseError) carry\n // their own name — classification is instanceof, never name-matching.\n override readonly name: string = 'DeterministicJobError';\n}\n\n/**\n * The taxonomy, and its provider coupling — THE part that will drift:\n *\n * - `@anthropic-ai/sdk` errors carry a numeric `status`. 408/429/5xx are\n * environmental (throttles, overload, gateway weather) → transient. The\n * remaining 4xx mean the request itself was rejected — invalid, too\n * large, unauthorized — and re-sending it unchanged is guaranteed waste\n * → deterministic.\n * - Aborts (`APIUserAbortError` from the SDK, `AbortError` from fetch/mock)\n * are our own bound or shutdown tearing the transport down — nothing was\n * judged → transient.\n * - Ollama surfaces plain `Error`s (no status) and network failures as\n * `TypeError: fetch failed`; the SDK's `MessageStream terminated` is a\n * dropped connection. All land `undefined` → retryable, the safe default.\n * - `@semiont/inference`'s `StructuredReadError` carries the stop reason\n * because the cause classifies differently: `max_tokens` is truncation of\n * an over-demanded answer — the adapter throws before the caller's\n * `assertNotTruncated` can see the stop reason, so the classification\n * must happen on the typed error itself (measured live 2026-09-02) —\n * while any other reason is model misbehavior a retry may fix.\n */\nexport function classifyFailure(error: unknown): FailureClass | undefined {\n if (error instanceof DeterministicJobError) return 'deterministic';\n if (error instanceof InferenceTimeoutError) return 'transient';\n if (error instanceof StructuredReadError && error.stopReason === 'max_tokens') return 'deterministic';\n if (!isObject(error)) return undefined;\n\n const name = isString(error.name) ? error.name : '';\n if (name === 'DeterministicJobError') return 'deterministic';\n if (name === 'APIUserAbortError' || name === 'AbortError') return 'transient';\n\n const status = isNumber(error.status) ? error.status : undefined;\n if (status !== undefined) {\n if (status === 408 || status === 429 || status >= 500) return 'transient';\n if (status >= 400) return 'deterministic';\n }\n\n return undefined;\n}\n","/**\n * Detection budget derivation — pure window arithmetic over the inference\n * provider's published limits. No hand-tuned chunk constants, no density or\n * yield modeling: document content never enters this arithmetic. The guard\n * for the pathological tail (a chunk whose annotation JSON still overflows\n * the output budget) is `assertNotTruncated` below — invoked per chunk by\n * the callers on every response, not a prediction here.\n *\n * Provider shapes (see `@semiont/inference` interface.ts):\n * - Shared window (Ollama): the provider publishes\n * `maxOutputTokens === contextTokens` — prompt and response share one\n * `num_ctx`. What remains after the prompt scaffold is split\n * input:output = 1:2 (annotation JSON echoes each span plus a fixed\n * key/context envelope, so output needs the larger share). The 1:2 ratio\n * is the plan's one allocation policy — doc-independent, tuned only on\n * live evidence.\n * - Separate ceilings (Anthropic): output takes its full ceiling (duration-\n * capped below), and input follows the same 1:2 allocation — never more\n * than half the output budget. Input does NOT get \"the rest of the\n * window\": measured 2026-09-02, a window-sized chunk of entity-dense text\n * demands more output than any budget holds, so the model grinds toward\n * max_tokens for minutes (killed at the call bound as a \"stall\") or\n * collapses to the degenerate []. Large documents chunk; that is the fix,\n * not a cost.\n */\n\nimport { chunkText, type ChunkingConfig, type Logger } from '@semiont/core';\nimport { StructuredReadError, type InferenceLimits, type TokenUsage } from '@semiont/inference';\nimport { recordDetectionCall } from '@semiont/observability';\nimport { DeterministicJobError } from '../../failure-class';\nimport { INFERENCE_TIMEOUT_MS, InferenceTimeoutError } from '../inference-call';\n\n/**\n * A `max_tokens` stop reason means the model's JSON was cut off mid-stream.\n * Structured output serializes whatever was generated, so that still yields\n * a syntactically-valid but incomplete array — it would parse cleanly and\n * silently under-report. Fail the job loudly instead. With derived budgets\n * this fires only on pathological annotation density.\n *\n * ONE decider for every detection path (entity extraction and the four\n * motivations) — the classification must not diverge between them: same\n * input truncates the same way, so a retry is guaranteed waste and the\n * throw carries the deterministic class (ABANDONED-INFERENCE P3, A4).\n */\nexport function assertNotTruncated(response: { stopReason: string }, label: string, chunk: number, totalChunks: number, outputBudget: number): void {\n if (response.stopReason === 'max_tokens') {\n throw new DeterministicJobError(`${label} response truncated (max_tokens) on chunk ${chunk}/${totalChunks} despite the derived output budget of ${outputBudget} tokens — failing the job rather than under-reporting annotations.`);\n }\n}\n\n/**\n * `reconcileSelector` disambiguates a span with up to 64 chars of prefix and\n * 64 of suffix (the annotation-selector schema). Overlap must let a span\n * sitting at a chunk boundary carry that context — plus a span allowance of\n * the same order — into the adjacent chunk. Schema-derived, not tuned.\n */\nconst SELECTOR_CONTEXT_CHARS = 64;\nconst OVERLAP_CHARS =\n SELECTOR_CONTEXT_CHARS + // prefix\n SELECTOR_CONTEXT_CHARS + // suffix\n 2 * SELECTOR_CONTEXT_CHARS; // span allowance\n/** ~4 chars/token — the same heuristic `estimateTokens`/`chunkText` use. */\nconst OVERLAP_TOKENS = Math.ceil(OVERLAP_CHARS / 4);\n\n/**\n * One temperature for every detection call. Detection copies spans verbatim\n * against a closed type vocabulary — a fidelity task, so determinism is the\n * right default (reproducible re-runs). Measured equal to hotter settings\n * on yield and consistency at production call sizes.\n */\nexport const DETECTION_TEMPERATURE = 0;\n\n/**\n * Assumed worst-case output rate for providers that publish none\n * (OLLAMA-DETECTION-TESTING P3b, from F9). Rate-silent providers (Ollama —\n * local hardware, rate unknowable a priori) previously got NO duration bound:\n * capacity-sizing handed a 262K-window model a ~174K-token output budget, and\n * a repetition loop then burned the full 10-minute guillotine as TRANSIENT —\n * retried identically. Under a duration-shaped cap the same loop dies in\n * minutes as max_tokens → deterministic → subdividable: the useful failure.\n *\n * 30 tok/s. P2 measured 36–90 tok/s across three local model families\n * (qwen3.5:9b ~36, gemma4:26b ~50, gemma4:e2b ~90), and the half-bound rule\n * in the cap below means real rates down to 15 tok/s still finish inside the\n * guillotine — anything slower is wedged, not working, which is exactly what\n * the guillotine is for. A conservative floor, deliberately NOT per-model:\n * F8 showed per-model thresholds chase noise. Owned policy constant, same\n * status as the 1:2 split and DETECTION_TEMPERATURE.\n */\nexport const ASSUMED_OUTPUT_TOKENS_PER_HOUR = 108_000;\n\n/**\n * The count-verifier band (OLLAMA-DETECTION-TESTING P3c, user-ratified\n * 2026-09-05): an extraction that found fewer than 1/BAND of the mentions a\n * cheap count call reports is flagged as silent yield collapse (F7). The band\n * is LOAD-BEARING at 2: F12 measured the same model flipping between\n * entity-like and mention-like enumeration on the same corpus, and that\n * legitimate judgment spread must fit INSIDE the band — probe evidence (n=6)\n * put every healthy ratio at 0.68–0.88 and every collapse at 0.18–0.41.\n */\nexport const YIELD_COLLAPSE_BAND = 2;\n\n/**\n * The silent-yield-collapse verdict (F7): a schema-clean, done-reason-clean\n * extraction that found a fraction of what a cheap count call says is present.\n * Extends DeterministicJobError because the collapse is MEASURED deterministic\n * — bit-identical across retries and across budget regimes — so a retry is\n * guaranteed waste; the classification and the size-floored subdivision\n * descent both follow from the base class. It diverges from truncation at the\n * size floor: NO re-roll (a same-size re-roll provably returns the identical\n * collapse), and — ruled 2026-09-05 after P4 attempt 1, amending the original\n * fail-the-job invariant — the floor ACCEPTS the flagged piece's `salvage`\n * loudly rather than failing the unit: one hostile ~530-char stretch had\n * discarded ~20 chunks of good extraction, and at floor sizes the count's\n * evidence sits far below anything the probe validated. The warning and the\n * 'collapsed' telemetry rows are the durable record; re-detection heals.\n */\nexport class YieldCollapseError extends DeterministicJobError {\n override readonly name = 'YieldCollapseError';\n /** What the flagged extraction DID find — every span write-time-verified,\n * so discarding it at the floor would add loss on top of the under-report.\n * Carried on the error because the flag site cannot know whether descent\n * remains possible; the floor is the subdivider's knowledge. */\n constructor(message: string, readonly salvage: unknown[] = []) {\n super(message);\n }\n}\n\nexport interface DetectionBudget {\n /** Feed to `chunkText` — `chunkSize` is the derived input budget (tokens). */\n chunking: ChunkingConfig;\n /** Pass as `maxTokens` on every per-chunk inference call. */\n outputBudget: number;\n}\n\n/**\n * Derive the per-call input/output token budgets for a detection job from the\n * provider's actual limits and the measured prompt scaffold.\n *\n * @param limits - the provider's published ceilings (`client.limits()`)\n * @param scaffoldTokens - tokens of the prompt template around the content,\n * measured from the actually-built template (e.g. `estimateTokens(build(''))`)\n * @param typesPerCall - how many entity types (or equivalent span families)\n * ONE call asks for: output demand scales with it, so the allocation\n * divides by it. The per-type loop passes 1; a future multi-type batch\n * passes its batch size.\n * @throws when the window is too small to hold the scaffold plus a useful\n * chunk — fail-loud, same family as the truncation and window guards.\n */\nexport function deriveDetectionBudget(\n limits: InferenceLimits,\n scaffoldTokens: number,\n typesPerCall: number,\n): DetectionBudget {\n const { contextTokens, maxOutputTokens } = limits;\n const available = contextTokens - scaffoldTokens;\n\n let inputBudget: number;\n let outputBudget: number;\n\n if (maxOutputTokens >= contextTokens) {\n // Shared window: 1:2 input:output split of what the scaffold leaves.\n inputBudget = Math.floor(available / 3);\n outputBudget = available - inputBudget;\n } else {\n outputBudget = maxOutputTokens;\n inputBudget = contextTokens - outputBudget - scaffoldTokens;\n if (inputBudget <= 0) {\n // Degenerate separate-ceilings shape (output ceiling nearly fills the\n // window): fall back to the shared split rather than starving input.\n inputBudget = Math.floor(available / 3);\n outputBudget = available - inputBudget;\n }\n }\n\n // Duration floor (ABANDONED-INFERENCE P4 HD3; universalized by\n // OLLAMA-DETECTION-TESTING P3b): cap each call's output at what the\n // provider's worst-case rate finishes inside our own inference bound — a\n // call projected past the 10-minute guillotine is planned-to-fail — and\n // scale input by the same factor, so the input:output ratio (and with it\n // the per-chunk truncation risk profile) is exactly the capacity\n // solution's. Capacity says what CAN fit in one call; this says what\n // SHOULD. EVERY provider gets the bound: the published rate when there is\n // one, the conservative assumed floor when there is not — a rate-silent\n // provider with no bound turned repetition loops into hour-long transient\n // burns (F9; see ASSUMED_OUTPUT_TOKENS_PER_HOUR). It is a floor on chunk\n // count, never a raise (a tighter capacity budget is left alone). Side\n // effect worth knowing: on Anthropic this lands every detection call at\n // or under the SDK's non-streaming threshold — off the MessageStream path\n // the original `terminated` failure arrived on.\n const outputTokensPerHour = limits.outputTokensPerHour ?? ASSUMED_OUTPUT_TOKENS_PER_HOUR;\n {\n // Half the bound, not all of it: at the full bound the slowest\n // legitimate full-budget generation lands exactly on the guillotine.\n // At half, the guillotine fires only on calls at least 2× slower than\n // the provider's own worst-case model — wedged, not working.\n const durationSafeOutput = Math.floor(outputTokensPerHour * (INFERENCE_TIMEOUT_MS / 2) / 3_600_000);\n if (outputBudget > durationSafeOutput) {\n inputBudget = Math.floor(inputBudget * (durationSafeOutput / outputBudget));\n outputBudget = durationSafeOutput;\n }\n }\n\n // The 1:2 allocation policy, applied to every provider shape (the shared\n // split satisfies it by construction; separate ceilings did not). Output\n // must hold an annotation echo of every span in the chunk plus its\n // key/context envelope, so a chunk larger than half the output budget is\n // a call whose honest answer cannot fit — the failure measured live on\n // 2026-09-02 (silent multi-minute grinds to max_tokens, degenerate []).\n // The demand is PER TYPE ASKED FOR, so a K-type call divides the input\n // share by K. Still no density modeling: same one policy, content never\n // enters.\n inputBudget = Math.min(inputBudget, Math.floor(outputBudget / (2 * typesPerCall)));\n\n if (inputBudget <= OVERLAP_TOKENS) {\n throw new Error(\n `Inference window too small for detection: context ${contextTokens} tokens minus scaffold ${scaffoldTokens} leaves an input budget of ${inputBudget} (need > ${OVERLAP_TOKENS}). Use a model with a larger context window or reduce the prompt scaffold.`,\n );\n }\n\n return {\n chunking: { chunkSize: inputBudget, overlap: OVERLAP_TOKENS },\n outputBudget,\n };\n}\n\n/**\n * Depth cap for TIMEOUTS only: a call still timing out on a quarter-sized\n * chunk is not a size problem, and timeouts classify transient — the\n * job-level retry is their second chance. Truncations descend by size\n * instead (see `callChunkSubdividing`).\n */\nexport const MAX_SUBDIVISION_DEPTH = 2;\n\n/** The failures a smaller chunk can plausibly fix. An unreadable response\n * that stopped naturally is model misbehavior, not size. */\n/** The F3 shape: unreadable output whose stop reason is UNKNOWN (done_reason\n * absent). Held not-subdividable through P3a for lack of evidence; the live\n * gate then reproduced it twice on real text (2026-09-03 at ~21K chars, P4\n * attempt 2 at ~4.5K, mid-descent, adjacent to max_tokens truncations that\n * healed by subdividing) and measured the retry-at-same-size alternative as a\n * deterministic 34 s burn. Size-shaped on the evidence — descends like\n * truncation, but with no floor re-roll (near-deterministic, nothing salvaged)\n * and unchanged retryable classification (a genuinely broken server still\n * deserves its budget). */\nfunction unknownUnreadable(error: unknown): boolean {\n return error instanceof StructuredReadError && error.stopReason === 'unknown';\n}\n\nfunction subdividable(error: unknown): boolean {\n return error instanceof InferenceTimeoutError || truncation(error) || unknownUnreadable(error);\n}\n\n/** Truncation in either surface: parsed-but-flagged (`assertNotTruncated`'s\n * `DeterministicJobError`) or cut off mid-JSON (`StructuredReadError` with\n * `max_tokens`). */\nfunction truncation(error: unknown): boolean {\n return (\n error instanceof DeterministicJobError ||\n (error instanceof StructuredReadError && error.stopReason === 'max_tokens')\n );\n}\n\n/**\n * Run one chunk's inference call, subdividing IN PLACE when it fails in a\n * way a smaller chunk can fix — instead of burning the whole attempt to\n * come back at the same size. Sub-pieces re-use the caller's overlap, so\n * spans straddling a split are caught twice and fall to the downstream\n * span-keyed dedupe. On a failure subdivision cannot fix, the ORIGINAL\n * error propagates so classification sees what actually happened.\n */\nexport interface ChunkCallResult<T> {\n items: T[];\n /** The provider's own token counts, when it reported any. Never estimated. */\n usage?: TokenUsage;\n}\n\n/** Which shape of failure this was — they demand opposite responses, so the\n * history must tell them apart: truncation descends by size, a timeout fails\n * fast, anything else is not size-shaped at all. */\nfunction outcomeOf(error: unknown): 'truncated' | 'timeout' | 'collapsed' | 'error' {\n // Collapse before truncation: it IS a DeterministicJobError (so the\n // truncation check would swallow it), but in the history the two are\n // different facts — one is output overflow, the other silent under-report.\n if (error instanceof YieldCollapseError) return 'collapsed';\n if (truncation(error)) return 'truncated';\n if (error instanceof InferenceTimeoutError) return 'timeout';\n return 'error';\n}\n\nexport async function callChunkSubdividing<T>(\n label: string,\n chunk: string,\n chunking: ChunkingConfig,\n call: (piece: string) => Promise<ChunkCallResult<T>>,\n logger?: Logger,\n): Promise<T[]> {\n // One telemetry record per model call, successes AND failures\n // (DETECTION-QUALITY-THROUGHPUT P1). This is the only place `depth` and\n // `reroll` exist, so it is the only place a complete record can be written.\n async function recorded(piece: string, depth: number, reroll: boolean): Promise<ChunkCallResult<T>> {\n const start = performance.now();\n try {\n const result = await call(piece);\n recordDetectionCall({\n label, pieceChars: piece.length, durationMs: performance.now() - start,\n items: result.items.length, depth, reroll, outcome: 'success',\n ...(result.usage ? { inputTokens: result.usage.inputTokens, outputTokens: result.usage.outputTokens } : {}),\n });\n return result;\n } catch (error) {\n // A failed call still cost its input and its wall time — the descent's\n // price is invisible without it.\n recordDetectionCall({\n label, pieceChars: piece.length, durationMs: performance.now() - start,\n items: 0, depth, reroll, outcome: outcomeOf(error),\n });\n throw error;\n }\n }\n\n async function attempt(piece: string, chunkSize: number, depth: number): Promise<T[]> {\n try {\n return (await recorded(piece, depth, false)).items;\n } catch (error) {\n if (!subdividable(error)) throw error;\n const half = Math.floor(chunkSize / 2);\n // Truncation descends by SIZE: demand halves with each subdivision,\n // so descent terminates — and list-dense text (registers, indexes)\n // honestly yields several times its input in annotation output, so\n // the descent must go as deep as the content demands. The floor\n // derives from the overlap constant; below it even solid names fit\n // the budget. Timeouts stay depth-capped and fail fast.\n //\n // A descent must also actually CHANGE the input: once a piece fits\n // inside the smaller chunk size, re-chunking returns it unchanged, and\n // at temperature 0 the identical call returns the identical failure —\n // measured live (P4 attempt 1: one 572-char piece \"descended\" through\n // three depths, same verdict each time). A piece that cannot shrink is\n // AT its floor, whatever the arithmetic floor says.\n const pieces = chunkText(piece, { chunkSize: half, overlap: chunking.overlap });\n const shrinks = pieces.length > 1 || pieces[0] !== piece;\n // Size-shaped failures (truncation, collapse, the F3 unknown-unreadable)\n // descend to the SIZE floor; only timeouts are depth-capped — the F3 fix\n // must reach failures that first appear mid-descent (measured at depth 3).\n const canDescend = shrinks && (truncation(error) || unknownUnreadable(error)\n ? half > 2 * OVERLAP_TOKENS\n : depth < MAX_SUBDIVISION_DEPTH);\n if (!canDescend) {\n // A collapse verdict at the floor is ACCEPTED, loudly (ruled\n // 2026-09-05): its salvage flows through with a warning instead of\n // one hostile piece discarding the whole unit's work. No re-roll —\n // the collapse is deterministic, a same-size retry changes nothing.\n if (error instanceof YieldCollapseError) {\n logger?.warn('Floor-size piece still flagged as collapsed — accepting its under-reported salvage and continuing', {\n pieceChars: piece.length,\n salvaged: error.salvage.length,\n error: error.message,\n });\n return error.salvage as T[];\n }\n // At the size floor honest overflow is impossible, so truncation\n // here is a degeneration loop — a sampling accident. One same-size\n // re-roll; a second truncation propagates. Timeouts get no re-roll.\n if (!truncation(error)) throw error;\n logger?.warn('Floor-size piece truncated — re-rolling once before giving up', {\n pieceChars: piece.length,\n error: error instanceof Error ? error.message : String(error),\n });\n return (await recorded(piece, depth, true)).items;\n }\n logger?.warn('Chunk call failed at a size-shaped bound — subdividing and retrying smaller', {\n depth: depth + 1,\n pieceChars: piece.length,\n nextChunkSizeTokens: half,\n error: error instanceof Error ? error.message : String(error),\n });\n const collected: T[] = [];\n for (const p of pieces) {\n collected.push(...(await attempt(p, half, depth + 1)));\n }\n return collected;\n }\n }\n return attempt(chunk, chunking.chunkSize, 0);\n}\n","/**\n * Prompt builders for annotation detection motivations\n *\n * Provides static methods to build AI prompts for each Web Annotation motivation type.\n * Extracted from worker implementations to centralize prompt logic.\n *\n * Locale handling: builders take two optional BCP-47 tags. `language` is the\n * locale the *generated body text* should be written in (sourced from the user's\n * UI locale); `sourceLanguage` is the locale of the *text being analyzed*\n * (sourced from `ResourceDescriptor` / its primary representation). They are\n * independent — see `types.ts` \"Locale conventions\" for the full discussion.\n */\n\nimport { getLocaleEnglishName } from '@semiont/core';\n\nfunction languageName(tag: string): string {\n return getLocaleEnglishName(tag) || tag;\n}\n\n/** Returns \"Source text language: <Name>.\" or empty string. */\nfunction sourceLanguageGuidance(sourceLanguage?: string): string {\n if (!sourceLanguage) return '';\n return `\\n\\nSource text language: ${languageName(sourceLanguage)}.`;\n}\n\n/** Returns \"Write your <kind> in <Name>.\" or empty string. */\nfunction bodyLanguageGuidance(language: string | undefined, kind: string): string {\n if (!language || language === 'en') return '';\n return `\\n\\nIMPORTANT: Write your ${kind} in ${languageName(language)}.`;\n}\n\nexport class MotivationPrompts {\n /**\n * Build a prompt for detecting comment-worthy passages\n *\n * @param content - The text content to analyze — a chunk sized by the\n * caller from derived provider limits; NEVER re-truncated here (a\n * builder-level clip is silent input loss — see #738)\n * @param instructions - Optional user-provided instructions\n * @param tone - Optional tone guidance (e.g., \"academic\", \"conversational\")\n * @param density - Optional target number of comments per 2000 words\n * @returns Formatted prompt string\n */\n static buildCommentPrompt(\n content: string,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string\n ): string {\n let prompt: string;\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n const bodyLang = bodyLanguageGuidance(language, 'comments');\n\n if (instructions) {\n // User provided specific instructions - minimal prompt, let instructions drive behavior\n const toneGuidance = tone ? ` Use a ${tone} tone.` : '';\n const densityGuidance = density\n ? `\\n\\nAim for approximately ${density} comments per 2000 words of text.`\n : ''; // Let user instructions determine density\n\n prompt = `Add comments to passages in this text following these instructions:\n\n${instructions}${toneGuidance}${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of comments. Each comment must have:\n- \"exact\": the exact text passage being commented on (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"comment\": your comment following the instructions above\n\nRespond with a valid JSON array.\n\nExample:\n[\n {\"exact\": \"the quarterly review meeting\", \"prefix\": \"We need to schedule \", \"suffix\": \" for next month.\", \"comment\": \"Who will lead this? Should we invite the external auditors?\"}\n]`;\n } else {\n // No specific instructions - fall back to explanatory/educational mode\n const toneGuidance = tone\n ? `\\n\\nTone: Use a ${tone} style in your comments.`\n : '';\n const densityGuidance = density\n ? `\\n- Aim for approximately ${density} comments per 2000 words`\n : `\\n- Aim for 3-8 comments per 2000 words (not too sparse or dense)`;\n\n prompt = `Identify passages in this text that would benefit from explanatory comments.\nFor each passage, provide contextual information, clarification, or background.${toneGuidance}\n\nGuidelines:\n- Select passages that reference technical terms, historical figures, complex concepts, or unclear references\n- Provide comments that ADD VALUE beyond restating the text\n- Focus on explanation, background, or connections to other ideas\n- Avoid obvious or trivial comments\n- Keep comments concise (1-3 sentences typically)${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of comments. Each comment should have:\n- \"exact\": the exact text passage being commented on (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"comment\": your explanatory comment (1-3 sentences, provide context/background/clarification)\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"Ouranos\", \"prefix\": \"In the beginning, \", \"suffix\": \" ruled the universe\", \"comment\": \"Ouranos (also spelled Uranus) is the primordial Greek deity personifying the sky. In Hesiod's Theogony, he is the son and husband of Gaia (Earth) and father of the Titans.\"}\n]`;\n }\n\n return prompt;\n }\n\n /**\n * Build a prompt for detecting highlight-worthy passages\n *\n * @param content - The text content to analyze — a chunk sized by the\n * caller from derived provider limits; NEVER re-truncated here (a\n * builder-level clip is silent input loss — see #738)\n * @param instructions - Optional user-provided instructions\n * @param density - Optional target number of highlights per 2000 words\n * @returns Formatted prompt string\n */\n static buildHighlightPrompt(\n content: string,\n instructions?: string,\n density?: number,\n sourceLanguage?: string\n ): string {\n let prompt: string;\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n\n if (instructions) {\n // User provided specific instructions - minimal prompt, let instructions drive behavior\n const densityGuidance = density\n ? `\\n\\nAim for approximately ${density} highlights per 2000 words of text.`\n : ''; // Let user instructions determine density\n\n prompt = `Identify passages in this text to highlight following these instructions:\n\n${instructions}${densityGuidance}${sourceLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of highlights. Each highlight must have:\n- \"exact\": the exact text passage to highlight (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n\nRespond with a valid JSON array.\n\nExample:\n[\n {\"exact\": \"revenue grew 45% year-over-year\", \"prefix\": \"In Q3 2024, \", \"suffix\": \", exceeding all forecasts.\"}\n]`;\n } else {\n // No specific instructions - fall back to importance/salience mode\n const densityGuidance = density\n ? `\\n- Aim for approximately ${density} highlights per 2000 words`\n : `\\n- Aim for 3-8 highlights per 2000 words (be selective)`;\n\n prompt = `Identify passages in this text that merit highlighting for their importance or salience.\nFocus on content that readers should notice and remember.\n\nGuidelines:\n- Highlight key claims, findings, or conclusions\n- Highlight important definitions, terminology, or concepts\n- Highlight notable quotes or particularly striking statements\n- Highlight critical decisions, action items, or turning points\n- Select passages that are SIGNIFICANT, not just interesting\n- Avoid trivial or obvious content${densityGuidance}${sourceLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of highlights. Each highlight should have:\n- \"exact\": the exact text passage to highlight (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"we will discontinue support for legacy systems by March 2025\", \"prefix\": \"After careful consideration, \", \"suffix\": \". This decision affects\"}\n]`;\n }\n\n return prompt;\n }\n\n /**\n * Build a prompt for detecting assessment-worthy passages\n *\n * @param content - The text content to analyze — a chunk sized by the\n * caller from derived provider limits; NEVER re-truncated here (a\n * builder-level clip is silent input loss — see #738)\n * @param instructions - Optional user-provided instructions\n * @param tone - Optional tone guidance (e.g., \"critical\", \"supportive\")\n * @param density - Optional target number of assessments per 2000 words\n * @returns Formatted prompt string\n */\n static buildAssessmentPrompt(\n content: string,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string\n ): string {\n let prompt: string;\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n const bodyLang = bodyLanguageGuidance(language, 'assessments');\n\n if (instructions) {\n // User provided specific instructions - minimal prompt, let instructions drive behavior\n const toneGuidance = tone ? ` Use a ${tone} tone.` : '';\n const densityGuidance = density\n ? `\\n\\nAim for approximately ${density} assessments per 2000 words of text.`\n : ''; // Let user instructions determine density\n\n prompt = `Assess passages in this text following these instructions:\n\n${instructions}${toneGuidance}${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of assessments. Each assessment must have:\n- \"exact\": the exact text passage being assessed (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"assessment\": your assessment following the instructions above\n\nRespond with a valid JSON array.\n\nExample:\n[\n {\"exact\": \"the quarterly revenue target\", \"prefix\": \"We established \", \"suffix\": \" for Q4 2024.\", \"assessment\": \"This target seems ambitious given market conditions. Consider revising based on recent trends.\"}\n]`;\n } else {\n // No specific instructions - fall back to analytical/evaluation mode\n const toneGuidance = tone\n ? `\\n\\nTone: Use a ${tone} style in your assessments.`\n : '';\n const densityGuidance = density\n ? `\\n- Aim for approximately ${density} assessments per 2000 words`\n : `\\n- Aim for 2-6 assessments per 2000 words (focus on key passages)`;\n\n prompt = `Identify passages in this text that merit critical assessment or evaluation.\nFor each passage, provide analysis of its validity, strength, or implications.${toneGuidance}\n\nGuidelines:\n- Select passages containing claims, arguments, conclusions, or assertions\n- Assess evidence quality, logical soundness, or practical implications\n- Provide assessments that ADD INSIGHT beyond restating the text\n- Focus on passages where evaluation would help readers form judgments\n- Keep assessments concise yet substantive (1-3 sentences typically)${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of assessments. Each assessment should have:\n- \"exact\": the exact text passage being assessed (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"assessment\": your analytical assessment (1-3 sentences, evaluate validity/strength/implications)\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"AI will replace most jobs by 2030\", \"prefix\": \"Many experts predict that \", \"suffix\": \", fundamentally reshaping\", \"assessment\": \"This claim lacks nuance and supporting evidence. Employment patterns historically show job transformation rather than wholesale replacement. The timeline appears speculative without specific sector analysis.\"}\n]`;\n }\n\n return prompt;\n }\n\n /**\n * Build a prompt for detecting structural tags\n *\n * @param content - The full text content to analyze (NOT truncated for structural analysis)\n * @param category - The specific category to detect\n * @param schemaName - Human-readable schema name\n * @param schemaDescription - Schema description\n * @param schemaDomain - Schema domain\n * @param categoryDescription - Category description\n * @param categoryExamples - Example questions/guidance for this category\n * @returns Formatted prompt string\n */\n static buildTagPrompt(\n content: string,\n category: string,\n schemaName: string,\n schemaDescription: string,\n schemaDomain: string,\n categoryDescription: string,\n categoryExamples: string[],\n sourceLanguage?: string\n ): string {\n // Tags are schema-defined identifiers (\"Issue\", \"Rule\") — the LLM doesn't\n // generate the category label, so there's no body-language guidance here.\n // Only `sourceLanguage` is wired into the prompt; `language` (body locale)\n // is consumed at the body-stamp site in `processors.ts`.\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n\n const prompt = `You are analyzing a text using the ${schemaName} framework.\n\nSchema: ${schemaDescription}\nDomain: ${schemaDomain}\n\nYour task: Identify passages that serve the structural role of \"${category}\".\n\nCategory: ${category}\nDescription: ${categoryDescription}\nKey questions:\n${categoryExamples.map(ex => `- ${ex}`).join('\\n')}\n\nGuidelines:\n- Focus on STRUCTURAL FUNCTION, not semantic content\n- A passage serves the \"${category}\" role if it performs this function in the document's structure\n- Look for passages that explicitly fulfill this role\n- Passages can be sentences, paragraphs, or sections\n- Aim for precision - only tag passages that clearly serve this structural role\n- Typical documents have 1-5 instances of each category (some may have 0)${sourceLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of tags. Each tag should have:\n- \"exact\": the exact text passage (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"What duty did the defendant owe?\", \"prefix\": \"The central question is: \", \"suffix\": \" This question must be\"},\n {\"exact\": \"In tort law, a duty of care is established when...\", \"prefix\": \"Legal framework:\\\\n\", \"suffix\": \"\\\\n\\\\nApplying this standard\"}\n]`;\n\n return prompt;\n }\n}\n","import type { AnchorMethod, Logger } from '@semiont/core';\nimport { recordAnchorOutcome } from '@semiont/observability';\n\n/**\n * The anchoring methods that picked a plausible occurrence rather than a\n * certain one: `first-of-many` had several candidates and no usable context,\n * `fuzzy-match` recovered through case/whitespace/Levenshtein. Neither is an\n * error — the write-time selector invariant still holds — but both are places\n * the span could be the wrong instance.\n *\n * ONE decider, deliberately. This classification lived in two places (the\n * reference path's inline warn and the motivation parsers' logger) that\n * happened to agree; two copies of a risk judgement is one copy too many.\n */\nconst DEGRADED: ReadonlySet<string> = new Set<AnchorMethod>(['first-of-many', 'fuzzy-match']);\n\n/**\n * Audit one anchoring outcome: counted always, warned only when degraded.\n *\n * Counting EVERY outcome is the point (P5). A warning tells an operator that\n * one anchor was uncertain; only a rate over all anchors says whether the\n * detection run was precise, and the rate is what the plan asks to put beside\n * the yield numbers.\n */\nexport function noteAnchor(label: string, exact: string, method: AnchorMethod, logger?: Logger): void {\n recordAnchorOutcome(label, method);\n if (!DEGRADED.has(method)) return;\n const detail = { text: exact, anchorMethod: method };\n if (logger) logger.warn('Annotation anchored via degraded method', { label, ...detail });\n else console.warn(`[${label}] anchored via ${method}: \"${exact}\"`);\n}\n","/**\n * Response parsers for annotation detection motivations\n *\n * Provides static methods to parse and validate AI responses for each motivation type.\n * Includes offset validation and correction logic.\n * Extracted from worker implementations to centralize parsing logic.\n *\n * NOTE: These are static utility methods without logger access.\n * Console statements kept for debugging - consider adding logger parameter in future.\n */\n\nimport { reconcileSelector, isObject, isString, type AnchorMethod } from '@semiont/core';\nimport type { ElementSchema } from '@semiont/inference';\nimport { noteAnchor } from './anchor-audit';\n\n// Parsers receive ALREADY-PARSED elements (`unknown[]`) from the structured\n// inference surface — `generateStructured` returns `T[]` or throws, so\n// \"could not read the model\" never reaches this layer and there is no string\n// to parse here (the former strict `parseJsonArray` moved into that contract,\n// as the earlier tolerant walker moved into it before). What remains here is\n// per-element structural validation (STRUCTURED-INFERENCE D5 — the last line\n// on the Ollama path and the schema/type drift guard) plus reconciliation\n// against the full document.\n//\n// Each element schema is declared ADJACENT to the Match interface it mirrors:\n// the schema constrains the wire, the interface is what the code consumes,\n// and nothing verifies they agree — adjacency is the drift guard.\n// `prefix`/`suffix` stay OUT of `required` deliberately: requiring them turns\n// \"sometimes absent\" into \"always present, sometimes empty\" (measured\n// 2026-08-06), an anchoring-path change Phase 3 examines before anyone\n// relies on it.\n\n/**\n * Represents a detected comment with validated position\n */\nexport interface CommentMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n comment: string;\n}\n\n/** Wire schema for one comment element — keep in lockstep with `CommentMatch`. */\nexport const COMMENT_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n comment: { type: 'string' },\n },\n required: ['exact', 'comment'],\n additionalProperties: false,\n};\n\n/**\n * Represents a detected highlight with validated position\n */\nexport interface HighlightMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n}\n\n/** Wire schema for one highlight element — keep in lockstep with `HighlightMatch`. */\nexport const HIGHLIGHT_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n },\n required: ['exact'],\n additionalProperties: false,\n};\n\n/**\n * Represents a detected assessment with validated position\n */\nexport interface AssessmentMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n assessment: string;\n}\n\n/** Wire schema for one assessment element — keep in lockstep with `AssessmentMatch`. */\nexport const ASSESSMENT_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n assessment: { type: 'string' },\n },\n required: ['exact', 'assessment'],\n additionalProperties: false,\n};\n\n/**\n * Represents a detected tag with validated position\n */\nexport interface TagMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n category: string;\n}\n\n/**\n * Wire schema for one tag element — keep in lockstep with `RawTagInput`\n * (the category is stamped by the caller, not emitted by the model).\n */\nexport const TAG_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n },\n required: ['exact'],\n additionalProperties: false,\n};\n\nexport class MotivationParsers {\n /**\n * Validate and reconcile structured comment elements.\n *\n * @param parsed - Already-parsed elements from the structured surface\n * @param content - Original content to validate offsets against\n * @returns Array of validated comment matches\n */\n static parseComments(parsed: unknown[], content: string): CommentMatch[] {\n\n const valid = parsed.filter((c): c is { exact: string; prefix?: string; suffix?: string; comment: string } =>\n isObject(c) &&\n isString(c.exact) &&\n isString(c.comment) &&\n c.comment.trim().length > 0\n );\n\n console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);\n\n const validatedComments: CommentMatch[] = [];\n for (const comment of valid) {\n const reconciled = reconcileSelector(content, {\n exact: comment.exact,\n ...(typeof comment.prefix === 'string' ? { prefix: comment.prefix } : {}),\n ...(typeof comment.suffix === 'string' ? { suffix: comment.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated comment \"${comment.exact}\"`);\n continue;\n }\n logAnchorMethod('comment', comment.exact, reconciled.anchorMethod);\n validatedComments.push({\n comment: comment.comment,\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n\n return validatedComments;\n }\n\n /**\n * Validate and reconcile structured highlight elements.\n *\n * @param parsed - Already-parsed elements from the structured surface\n * @param content - Original content to validate offsets against\n * @returns Array of validated highlight matches\n */\n static parseHighlights(parsed: unknown[], content: string): HighlightMatch[] {\n\n const highlights = parsed.filter((h): h is { exact: string; prefix?: string; suffix?: string } =>\n isObject(h) && isString(h.exact)\n );\n\n const validatedHighlights: HighlightMatch[] = [];\n for (const highlight of highlights) {\n const reconciled = reconcileSelector(content, {\n exact: highlight.exact,\n ...(typeof highlight.prefix === 'string' ? { prefix: highlight.prefix } : {}),\n ...(typeof highlight.suffix === 'string' ? { suffix: highlight.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated highlight \"${highlight.exact}\"`);\n continue;\n }\n logAnchorMethod('highlight', highlight.exact, reconciled.anchorMethod);\n validatedHighlights.push({\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n\n return validatedHighlights;\n }\n\n /**\n * Validate and reconcile structured assessment elements.\n *\n * @param parsed - Already-parsed elements from the structured surface\n * @param content - Original content to validate offsets against\n * @returns Array of validated assessment matches\n */\n static parseAssessments(parsed: unknown[], content: string): AssessmentMatch[] {\n\n const assessments = parsed.filter((a): a is { exact: string; prefix?: string; suffix?: string; assessment: string } =>\n isObject(a) && isString(a.exact) && isString(a.assessment)\n );\n\n const validatedAssessments: AssessmentMatch[] = [];\n for (const assessment of assessments) {\n const reconciled = reconcileSelector(content, {\n exact: assessment.exact,\n ...(typeof assessment.prefix === 'string' ? { prefix: assessment.prefix } : {}),\n ...(typeof assessment.suffix === 'string' ? { suffix: assessment.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated assessment \"${assessment.exact}\"`);\n continue;\n }\n logAnchorMethod('assessment', assessment.exact, reconciled.anchorMethod);\n validatedAssessments.push({\n assessment: assessment.assessment,\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n\n return validatedAssessments;\n }\n\n /**\n * Validate structured tag elements into raw, pre-reconciliation tag inputs.\n * Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`\n * by anchoring `exact` against the source content.\n *\n * @param parsed - Already-parsed elements from the structured surface\n */\n static parseTags(parsed: unknown[]): RawTagInput[] {\n\n const valid = parsed.filter((t): t is RawTagInput =>\n isObject(t) && isString(t.exact) && t.exact.trim().length > 0\n );\n\n console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);\n\n return valid;\n }\n\n /**\n * Anchor raw tag inputs against source content and add category.\n */\n static validateTagOffsets(\n tags: RawTagInput[],\n content: string,\n category: string\n ): TagMatch[] {\n const validatedTags: TagMatch[] = [];\n for (const tag of tags) {\n const reconciled = reconcileSelector(content, {\n exact: tag.exact,\n ...(typeof tag.prefix === 'string' ? { prefix: tag.prefix } : {}),\n ...(typeof tag.suffix === 'string' ? { suffix: tag.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated tag \"${tag.exact}\" for category \"${category}\"`);\n continue;\n }\n logAnchorMethod('tag', tag.exact, reconciled.anchorMethod);\n validatedTags.push({\n category,\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n return validatedTags;\n }\n}\n\n/** Raw LLM-emitted tag, pre-reconciliation. */\nexport interface RawTagInput {\n exact: string;\n prefix?: string;\n suffix?: string;\n}\n\n/**\n * Audit one anchor-method classification. Forwards to the single decider\n * shared with the reference path (`anchor-audit.ts`) — which methods count as\n * risky is one judgement, and this file used to hold a second copy of it.\n */\nfunction logAnchorMethod(motivation: string, exact: string, anchorMethod: AnchorMethod): void {\n noteAnchor(motivation, exact, anchorMethod);\n}\n","/**\n * Annotation Detection\n *\n * Orchestrates the full annotation detection pipeline:\n * 1. Build AI prompts using MotivationPrompts\n * 2. Call AI inference\n * 3. Parse and validate results using MotivationParsers\n *\n * All methods take content as a string parameter — the worker process\n * fetches it and hands it in.\n */\n\nimport type { ElementSchema, InferenceClient } from '@semiont/inference';\nimport { chunkText, estimateTokens } from '@semiont/core';\nimport { boundedGenerateStructured } from './inference-call';\nimport { assertNotTruncated, callChunkSubdividing, deriveDetectionBudget, DETECTION_TEMPERATURE } from './detection/detection-chunking';\nimport { MotivationPrompts } from './detection/motivation-prompts';\nimport {\n MotivationParsers,\n COMMENT_ELEMENT_SCHEMA,\n HIGHLIGHT_ELEMENT_SCHEMA,\n ASSESSMENT_ELEMENT_SCHEMA,\n TAG_ELEMENT_SCHEMA,\n type CommentMatch,\n type HighlightMatch,\n type AssessmentMatch,\n type TagMatch,\n} from './detection/motivation-parsers';\nimport type { TagSchema } from '@semiont/core';\n\n/**\n * Per-chunk detection loop shared by the four motivations.\n *\n * Budgets derive from the provider's actual limits plus the measured prompt\n * scaffold (`buildPrompt('')`) — no literals. The prompt receives one chunk;\n * `parse` reconciles against the FULL document (the callers close over it),\n * so offsets index into the whole resource with no re-anchoring arithmetic.\n * Overlap duplicates pass through — the processor's span-keyed\n * `dedupeAnnotations` is the single dedupe point.\n *\n * `onActivity` fires whenever the detection is demonstrably alive: at each\n * chunk boundary (the count advances) AND periodically while one inference\n * call is in flight (the count repeats — liveness, not progress). Progress is\n * the worker's liveness heartbeat AND the client's inter-emission timeout\n * signal, so a silent single-chunk run kills a healthy job\n * (DETECTION-HEARTBEAT).\n */\nasync function detectInChunks<T>(\n client: InferenceClient,\n content: string,\n buildPrompt: (chunk: string) => string,\n motivation: string,\n elementSchema: ElementSchema,\n parse: (items: unknown[]) => T[],\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n): Promise<T[]> {\n const limits = await client.limits();\n const scaffoldTokens = estimateTokens(buildPrompt(''));\n // One motivation's spans per call — the single span family this prompt\n // asks for, the motivation-path analogue of one entity type.\n const { chunking, outputBudget } = deriveDetectionBudget(limits, scaffoldTokens, 1);\n const chunks = chunkText(content, chunking);\n\n const collected: T[] = [];\n for (let i = 0; i < chunks.length; i++) {\n // Structured surface: parsed elements or a throw — an unreadable model\n // response fails the job rather than reading as an empty detection. A\n // size-shaped failure (duration bound, truncation) subdivides in place\n // and retries smaller before it is allowed to fail the job.\n const items = await callChunkSubdividing<unknown>(motivation, chunks[i]!, chunking, async (piece) => {\n const response = await boundedGenerateStructured<unknown>(\n client, buildPrompt(piece), outputBudget, DETECTION_TEMPERATURE, elementSchema,\n // Still alive, same position (a long single call is otherwise silent).\n () => onActivity?.(i, chunks.length),\n );\n assertNotTruncated(response, `${motivation} detection`, i + 1, chunks.length, outputBudget);\n // Usage rides back so the telemetry record carries what the call COST\n // beside what it yielded — the provider's own counts, not an estimate.\n return { items: response.items, ...(response.usage ? { usage: response.usage } : {}) };\n });\n collected.push(...parse(items));\n if (i < chunks.length - 1) {\n onActivity?.(i + 1, chunks.length);\n }\n }\n return collected;\n}\n\nexport class AnnotationDetection {\n\n /**\n * Detect comments in content.\n *\n * `language` is the locale the LLM should write comment text in (annotation\n * body locale). `sourceLanguage` is the locale of the content being analyzed\n * (source-resource locale). See `types.ts` \"Locale conventions\" for the\n * full discussion.\n */\n static async detectComments(\n content: string,\n client: InferenceClient,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<CommentMatch[]> {\n return detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildCommentPrompt(chunk, instructions, tone, density, language, sourceLanguage),\n 'comment', COMMENT_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseComments(items, content),\n onActivity,\n );\n }\n\n /**\n * Detect highlights in content.\n *\n * Highlights have no body — only `sourceLanguage` (source-resource locale)\n * applies, used in the prompt so the LLM analyzes non-English source\n * correctly.\n */\n static async detectHighlights(\n content: string,\n client: InferenceClient,\n instructions?: string,\n density?: number,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<HighlightMatch[]> {\n return detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildHighlightPrompt(chunk, instructions, density, sourceLanguage),\n 'highlight', HIGHLIGHT_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseHighlights(items, content),\n onActivity,\n );\n }\n\n /**\n * Detect assessments in content.\n *\n * `language` is the locale the LLM should write assessment text in\n * (annotation body locale). `sourceLanguage` is the locale of the content\n * being analyzed (source-resource locale).\n */\n static async detectAssessments(\n content: string,\n client: InferenceClient,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<AssessmentMatch[]> {\n return detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildAssessmentPrompt(chunk, instructions, tone, density, language, sourceLanguage),\n 'assessment', ASSESSMENT_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseAssessments(items, content),\n onActivity,\n );\n }\n\n /**\n * Detect tags in content for a specific category.\n *\n * The full `TagSchema` is supplied by the dispatcher (resolved against\n * the per-KB tag-schema projection at job-creation time) so the worker\n * is independent of the registry.\n *\n * `sourceLanguage` is the locale of the content being analyzed. Body-locale\n * (`language`) doesn't influence the tag prompt — categories are schema\n * identifiers, not LLM-generated text — so it's consumed at the body-stamp\n * site, not here.\n */\n static async detectTags(\n content: string,\n client: InferenceClient,\n schema: TagSchema,\n category: string,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<TagMatch[]> {\n const categoryInfo = schema.tags.find((t) => t.name === category);\n if (!categoryInfo) {\n throw new Error(`Invalid category \"${category}\" for schema ${schema.id}`);\n }\n\n // Parse per chunk; anchor once against the full document afterward.\n const parsedTags = await detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildTagPrompt(\n chunk,\n category,\n schema.name,\n schema.description,\n schema.domain,\n categoryInfo.description,\n categoryInfo.examples,\n sourceLanguage\n ),\n 'tag', TAG_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseTags(items),\n onActivity,\n );\n return MotivationParsers.validateTagOffsets(parsedTags, content, category);\n }\n}\n","import type { ElementSchema, InferenceClient } from '@semiont/inference';\nimport { chunkText, estimateTokens, getLocaleEnglishName, isObject, isString, type Logger } from '@semiont/core';\nimport { boundedGenerateStructured, boundedGenerateWithMetadata } from '../inference-call';\nimport { assertNotTruncated, callChunkSubdividing, deriveDetectionBudget, DETECTION_TEMPERATURE, YIELD_COLLAPSE_BAND, YieldCollapseError } from './detection-chunking';\n\n/**\n * Entity reference extracted from text — pre-reconciliation.\n *\n * The LLM emits `exact` (verbatim text span), `entityType`, and optional\n * `prefix` / `suffix` context for disambiguation. Offsets are not asked\n * for — `reconcileSelector` computes them by anchoring `exact` against\n * the source content in the calling processor.\n */\nexport interface ExtractedEntity {\n exact: string;\n entityType: string;\n prefix?: string;\n suffix?: string;\n}\n\n/**\n * JSON Schema for one extracted entity — the provider-enforced shape.\n *\n * Declared adjacent to `ExtractedEntity` deliberately: the schema is what\n * constrains the wire and the interface is what the code consumes, and\n * nothing verifies they agree — adjacency is the drift guard. The\n * per-element `isObject`/`isString` checks below stay as the structural\n * backstop (STRUCTURED-INFERENCE D5).\n *\n * `prefix`/`suffix` are deliberately NOT in `required`: with all four\n * required, models return `\"prefix\": \"\"` instead of omitting the key\n * (measured 2026-08-06), turning \"sometimes absent\" into \"always present,\n * sometimes empty\" — an anchoring-path change Phase 3 re-examines before\n * anyone relies on it.\n */\nconst ENTITY_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n entityType: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n },\n required: ['exact', 'entityType'],\n additionalProperties: false,\n};\n\n/**\n * Extract entity references from text using AI.\n *\n * Locale: entity references' bodies are entity-type identifiers (not\n * LLM-generated natural-language text), so only `sourceLanguage` (source-\n * resource locale) is meaningful here — it's used in the prompt so the LLM\n * analyzes non-English source correctly. There's no body-locale parameter.\n *\n * @param text - The text to analyze\n * @param entityTypes - Array of entity types to detect (optionally with examples)\n * @param client - Inference client for AI operations\n * @param includeDescriptiveReferences - Include anaphoric/cataphoric references (default: false)\n * @param logger - Logger for entity-extraction diagnostics (parse failures,\n * anchor decisions, drops). Required so dropped/filtered entities never\n * disappear silently.\n * @param sourceLanguage - BCP-47 tag for the source content's language\n * @param onActivity - Invoked with (completedChunks, totalChunks) whenever\n * the extraction is demonstrably alive: at each chunk boundary (the count\n * advances) AND periodically while a single inference call is in flight\n * (the count repeats — liveness, not progress). The caller MUST forward\n * this to its progress channel: progress is the worker's liveness\n * heartbeat for the stall watchdog, and the client's timeout is an\n * INTER-EMISSION one, so a silent single-chunk run kills a healthy job\n * (DETECTION-HEARTBEAT).\n * @returns Array of extracted entities with their character offsets\n */\n/** Output cap for the count call: the answer is one number (≤7 digits), and a\n * tiny cap is itself the safety — a ~5-token output structurally cannot loop\n * or truncate the way the extraction's array can. */\nconst COUNT_MAX_TOKENS = 16;\n\n/** The count answer, read as its leading integer. \"Respond with only the\n * number\" is the prompt's contract, but a model that pads it (\"There are 50\")\n * still yields its verdict; anything with no integer yields none. */\nfunction parseCount(text: string): number | undefined {\n const m = text.trim().match(/\\d+/);\n return m ? Number(m[0]) : undefined;\n}\n\n/**\n * The count-verifier (OLLAMA-DETECTION-TESTING P3c): guard a SUCCESSFUL\n * extraction against silent yield collapse (F7) — a schema-clean response\n * carrying a fraction of the mentions present, measured deterministic and\n * classification-invisible, so nothing else can catch it.\n *\n * A cheap count call (\"respond with only the number\") sets the expectation\n * FROM THE SAME TEXT — corpus-free, per the no-input-assumptions principle.\n * An extraction under 1/BAND of the count throws the collapse verdict, which\n * subdivision treats like truncation (descend by size) except at the floor,\n * where it fails the job loudly (user-ratified).\n *\n * The verifier's own failure — count call errors, or answers with no number —\n * disables it for the chunk (warn, pass through): a safety net's outage must\n * not take down a healthy extraction. Known limit, documented in the bug\n * report: the count saturates past ~8K chars, so margins are cleanest at\n * post-descent sizes; it still flagged every measured collapse at 16K/32K.\n */\nasync function assertYieldNotCollapsed(\n client: InferenceClient,\n piece: string,\n items: readonly unknown[],\n entityTypesDescription: string,\n logger: Logger,\n): Promise<void> {\n const prompt = `Count every mention of: ${entityTypesDescription} in the following text. Repeated mentions of the same entity count separately. Respond with only the number.\n\nText:\n\"\"\"\n${piece}\n\"\"\"`;\n\n let counted: number | undefined;\n try {\n const response = await boundedGenerateWithMetadata(client, prompt, COUNT_MAX_TOKENS, DETECTION_TEMPERATURE, undefined, logger);\n counted = parseCount(response.text);\n } catch (err) {\n logger.warn('Count-verifier call failed — yield check skipped for this chunk', {\n pieceChars: piece.length,\n error: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n if (counted === undefined) {\n logger.warn('Count-verifier answer carried no number — yield check skipped for this chunk', { pieceChars: piece.length });\n return;\n }\n if (items.length * YIELD_COLLAPSE_BAND < counted) {\n // The salvage rides the error: descent discards it (a smaller re-run does\n // better), the floor accepts it (better than nothing, and every span is\n // write-time-verified).\n throw new YieldCollapseError(\n `Extraction found ${items.length} entities where a count call reports ~${counted} mentions (band ×${YIELD_COLLAPSE_BAND}) on a ${piece.length}-char chunk — silent yield collapse (F7): deterministic — a same-size retry returns the identical under-report.`,\n [...items],\n );\n }\n}\n\nexport async function extractEntities(\n exact: string,\n entityTypes: string[] | { type: string; examples?: string[] }[],\n client: InferenceClient,\n includeDescriptiveReferences: boolean,\n logger: Logger,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n): Promise<ExtractedEntity[]> {\n\n // Format entity types for the prompt\n const entityTypesDescription = entityTypes.map(et => {\n if (typeof et === 'string') {\n return et;\n }\n return et.examples && et.examples.length > 0\n ? `${et.type} (examples: ${et.examples.slice(0, 3).join(', ')})`\n : et.type;\n }).join(', ');\n\n // Build prompt with optional support for anaphoric/cataphoric references\n // Anaphora: references that point backward (e.g., \"John arrived. He was tired.\")\n // Cataphora: references that point forward (e.g., \"When she arrived, Mary was surprised.\")\n // When enabled, include substantive descriptive references beyond simple pronouns\n const descriptiveReferenceGuidance = includeDescriptiveReferences\n ? `\nInclude both:\n- Direct mentions (names, proper nouns)\n- Descriptive references (substantive phrases that refer to entities)\n\nFor descriptive references, include:\n- Definite descriptions: \"the Nobel laureate\", \"the tech giant\", \"the former president\"\n- Role-based references: \"the CEO\", \"the physicist\", \"the author\", \"the owner\", \"the contractor\"\n- Epithets with context: \"the Cupertino-based company\", \"the iPhone maker\"\n- References to entities even when identity is unknown or unspecified\n\nDo NOT include:\n- Simple pronouns alone: he, she, it, they, him, her, them\n- Generic determiners alone: this, that, these, those\n- Possessives without substance: his, her, their, its\n\nExamples:\n- For \"Marie Curie\", include \"the Nobel laureate\" and \"the physicist\" but NOT \"she\"\n- For an unknown person, include \"the owner\" or \"the contractor\" (role-based references count even when identity is unspecified)\n`\n : `\nFind direct mentions only (names, proper nouns). Do not include pronouns or descriptive references.\n`;\n\n const sourceLangGuidance = sourceLanguage\n ? `\\nSource text language: ${getLocaleEnglishName(sourceLanguage) || sourceLanguage}.\\n`\n : '';\n\n // The LLM is asked for `exact`, `prefix`, and `suffix` — no character\n // offsets. Offsets get computed by `reconcileSelector` against the\n // source content. Asking the model for offsets wastes tokens and\n // encourages it to fabricate where it shouldn't.\n const buildPrompt = (text: string): string => `Identify entity references in the following text. Look for mentions of: ${entityTypesDescription}.\n${descriptiveReferenceGuidance}${sourceLangGuidance}\nText to analyze:\n\"\"\"\n${text}\n\"\"\"\n\nRespond with a JSON array of entities found. Each entity should have:\n- exact: the exact text span from the input (quoted verbatim — character-for-character)\n- entityType: one of the provided entity types\n- prefix: up to 64 characters of text immediately before the entity (used to disambiguate when the same text appears more than once)\n- suffix: up to 64 characters of text immediately after the entity (same purpose)\n\nIf no entities are found, respond with an empty array [].\n\nExample output:\n[{\"exact\":\"Alice\",\"entityType\":\"Person\",\"prefix\":\"\",\"suffix\":\" went to\"},{\"exact\":\"Paris\",\"entityType\":\"Location\",\"prefix\":\"went to \",\"suffix\":\" yesterday\"}]`;\n\n // Budgets derive from the provider's actual limits + the measured scaffold\n // (the template around the content) — no literals. Input is chunked only\n // when the derived budget forces it; small documents make one call, as\n // before.\n const limits = await client.limits();\n // The provider DECLARES whether its extractions get count-verified\n // (universal for real providers since the 2026-09-05 ruling — unverified\n // completeness is not a savings). Jobs does no provider-specific switching:\n // whatever varies by provider is a capability on the InferenceClient\n // contract, read here like `maxConcurrency`.\n const verifyYield = client.verifyDetectionYield;\n const scaffoldTokens = estimateTokens(buildPrompt(''));\n // One call asks for every type in `entityTypes` — the processor's per-type\n // loop passes one, so this is 1 in production today.\n const { chunking, outputBudget } = deriveDetectionBudget(limits, scaffoldTokens, entityTypes.length);\n const chunks = chunkText(exact, chunking);\n\n logger.debug('Sending entity extraction request', {\n entityTypes: entityTypesDescription,\n chunks: chunks.length,\n chunkSizeTokens: chunking.chunkSize,\n outputBudget,\n });\n\n const collected: ExtractedEntity[] = [];\n for (let i = 0; i < chunks.length; i++) {\n // The structured surface returns parsed elements or THROWS — an\n // unreadable model response is a job failure, never a silent []. A\n // size-shaped failure (duration bound, truncation) subdivides in place\n // and retries smaller before it is allowed to fail the job.\n const items = await callChunkSubdividing<unknown>('reference', chunks[i]!, chunking, async (piece) => {\n const response = await boundedGenerateStructured<unknown>(\n client,\n buildPrompt(piece),\n outputBudget,\n DETECTION_TEMPERATURE,\n ENTITY_ELEMENT_SCHEMA,\n // Still alive, same position: a long single call would otherwise emit\n // nothing at all between start and finish.\n () => onActivity?.(i, chunks.length),\n logger,\n );\n logger.debug('Got entity extraction response', {\n chunk: i + 1,\n chunks: chunks.length,\n pieceChars: piece.length,\n items: response.items.length,\n });\n\n // Truncation is data loss, not \"no entities\" — check it BEFORE\n // consuming: a truncated structured response can still carry a valid\n // partial array, so the items themselves cannot signal the loss.\n assertNotTruncated(response, 'Entity extraction', i + 1, chunks.length, outputBudget);\n // And a CLEAN response can still be a silent under-report (F7) — the\n // count-verifier is the only signal for that, and a flag throws the\n // collapse verdict so subdivision changes the input.\n if (verifyYield) {\n await assertYieldNotCollapsed(client, piece, response.items, entityTypesDescription, logger);\n }\n // Usage rides back so the telemetry record carries what the call COST\n // beside what it yielded — the provider's own counts, not an estimate.\n return { items: response.items, ...(response.usage ? { usage: response.usage } : {}) };\n }, logger);\n\n for (const e of items) {\n // No dedupe here: overlap duplicates from adjacent chunks pass through\n // to the processor's span-keyed dedupeAnnotations — the single dedupe\n // point.\n if (isObject(e) && isString(e.exact) && isString(e.entityType)) {\n collected.push({\n exact: e.exact,\n entityType: e.entityType,\n ...(isString(e.prefix) ? { prefix: e.prefix } : {}),\n ...(isString(e.suffix) ? { suffix: e.suffix } : {}),\n });\n } else {\n logger.debug('Dropped malformed LLM entity', { entity: e });\n }\n }\n\n // Chunk boundary: the count advances (real progress).\n if (i < chunks.length - 1) {\n onActivity?.(i + 1, chunks.length);\n }\n }\n\n return collected;\n}\n","/**\n * Resource Generation\n *\n * Generates markdown resources from topics using AI inference.\n */\n\nimport { getLocaleEnglishName, deriveViews } from '@semiont/core';\nimport type { GatheredContext, Logger, SupportedMediaType } from '@semiont/core';\nimport type { InferenceClient } from '@semiont/inference';\nimport { boundedGenerateWithMetadata } from '../inference-call';\n\n\nfunction getLanguageName(locale: string): string {\n return getLocaleEnglishName(locale) || locale;\n}\n\n// Prompt-embedding caps — bound the context fed to the model (CONTEXT-IDENTIFIERS\n// D4: named constants; promote to caller-tunable options only if a consumer\n// actually hits the wall). A fact past these bounds never reaches the model.\nconst RESOURCE_CONTENT_CAP = 4000;\nconst SEMANTIC_MATCH_LIMIT = 3;\nconst SEMANTIC_MATCH_CHARS = 240;\n\n/**\n * Model-visible identifier handle for an embedded excerpt — the ONE bracket\n * convention across all context sections (CONTEXT-IDENTIFIERS D1/D2): resource id\n * always, annotation id as a suffix when the excerpt is annotation-derived.\n */\nfunction idLabel(resourceId: string, annotationId?: string): string {\n return `[${resourceId}${annotationId ? `/${annotationId}` : ''}]`;\n}\n\n/**\n * The canonical gateway default for maxTokens — a short-definition size. The\n * UI also initialises its field to 500 as a UX convenience, but the\n * authoritative fallback lives here so direct API callers get a sensible\n * limit when they omit the parameter. Exported so error messages elsewhere\n * can name the ceiling that actually applied (one constant, no drift).\n */\nexport const DEFAULT_MAX_TOKENS = 500;\n\n/**\n * Generate resource content using inference.\n *\n * Locale parameters: `locale` is the *body* locale — the language the\n * generated resource should be written in (sourced from the user's UI\n * locale). `sourceLanguage` is the *source* locale — the language of the\n * referenced resource whose context (selected passage, surrounding text)\n * is embedded into the prompt. They're independent: a German user can\n * generate German content from an English source resource. See\n * `types.ts` \"Locale conventions\" for the full discussion.\n */\nexport async function generateResourceFromTopic(\n topic: string,\n entityTypes: string[],\n client: InferenceClient,\n logger: Logger,\n userPrompt?: string,\n locale?: string,\n context?: GatheredContext,\n temperature?: number,\n maxTokens?: number,\n sourceLanguage?: string,\n outputMediaType: SupportedMediaType = 'text/markdown',\n task: string = 'resource',\n structure?: string,\n cite: boolean = false,\n repair?: { source: string; error: string }\n): Promise<{ title: string; content: string; truncated: boolean }> {\n logger.debug('Generating resource from topic', {\n topicPreview: topic.substring(0, 100),\n entityTypes,\n hasUserPrompt: !!userPrompt,\n locale,\n sourceLanguage,\n hasContext: !!context,\n temperature,\n maxTokens,\n outputMediaType,\n task,\n structure\n });\n\n // Use provided values or defaults.\n const finalTemperature = temperature ?? 0.7;\n const finalMaxTokens = maxTokens ?? DEFAULT_MAX_TOKENS;\n\n // Determine language instructions. Body locale (\"write the resource in X\")\n // and source locale (\"the embedded source text is in Y\") are independent —\n // a German user can ask for a German write-up of an English source.\n const languageInstruction = locale && locale !== 'en'\n ? `\\n\\nIMPORTANT: Write the entire resource in ${getLanguageName(locale)}.`\n : '';\n const sourceLanguageInstruction = sourceLanguage\n ? `\\n\\nThe source resource and embedded context are in ${getLanguageName(sourceLanguage)}.`\n : '';\n\n // ── Context sections — switch on the unified GatheredContext focus ─────────\n // Annotation focus drives the annotation + selected-passage sections; resource\n // focus drives a minimal resource anchor (the full grounding — summary,\n // suggestedReferences, content — is YIELD-FROM-RESOURCE Gap B). The graph and\n // semantic sections are shared base, rendered for either focus.\n let annotationSection = '';\n let contextSection = '';\n let resourceSection = '';\n let graphSection = '';\n\n if (context) {\n const { focus } = context;\n // The focal resource's id — equal to the id `buildKnowledgeGraph` (P3) anchored\n // the graph's main node on, so `deriveViews` resolves edges. Read directly off\n // the descriptor (a plain string on the generated type); there is no event id to\n // thread here as the matcher had.\n const mainResourceId =\n focus.kind === 'annotation'\n ? focus.sourceResource['@id']\n : focus.resource['@id'];\n const focalAnnotationId = focus.kind === 'annotation' ? focus.annotation.id : undefined;\n\n if (focus.kind === 'annotation') {\n const parts: string[] = [];\n parts.push(`- Annotation motivation: ${focus.annotation.motivation}`);\n parts.push(`- Source resource: ${focus.sourceResource.name} ${idLabel(mainResourceId)}`);\n // Include body text for commenting/assessing annotations\n const { motivation, body } = focus.annotation;\n if (motivation === 'commenting' || motivation === 'assessing') {\n const bodyItem = Array.isArray(body) ? body[0] : body;\n if (bodyItem && 'value' in bodyItem && bodyItem.value) {\n const label = motivation === 'commenting' ? 'Comment' : 'Assessment';\n parts.push(`- ${label}: ${bodyItem.value}`);\n }\n }\n // The wizard's Hint: supplements or replaces the selected text as the\n // subject signal (GatheredContext.json focus.userHint). The matcher folds\n // it into its search term the same way — the two consumers of one field.\n if (focus.userHint) {\n parts.push(`- User hint (steers what to generate): ${focus.userHint}`);\n }\n annotationSection = `\\n\\nAnnotation context:\\n${parts.join('\\n')}`;\n\n if (focus.selected) {\n const { before, text, after } = focus.selected;\n contextSection = `\\n\\nSource document context:\n---\n${before ? `...${before}` : ''}\n**[${text}]**\n${after ? `${after}...` : ''}\n---\n`;\n }\n } else {\n // Resource focus — ground in the focal resource's summary, suggested\n // references, and content (omit-empty). Content is capped per resource to\n // bound prompt size; the caller already chose breadth via gather.resource.\n const parts = [`- Resource: ${focus.resource.name} ${idLabel(mainResourceId)}`];\n if (focus.summary) parts.push(`- Summary: ${focus.summary}`);\n if (focus.suggestedReferences && focus.suggestedReferences.length > 0) {\n parts.push(`- Suggested references: ${focus.suggestedReferences.join(', ')}`);\n }\n resourceSection = `\\n\\nResource context:\\n${parts.join('\\n')}`;\n\n if (focus.content?.main) {\n resourceSection += `\\n\\nResource content:\\n---\\n${focus.content.main.slice(0, RESOURCE_CONTENT_CAP)}\\n---`;\n }\n const related = Object.entries(focus.content?.related ?? {});\n if (related.length > 0) {\n const blocks = related\n .map(([id, text]) => `[${id}]\\n${text.slice(0, RESOURCE_CONTENT_CAP)}`)\n .join('\\n\\n');\n resourceSection += `\\n\\nRelated resource content:\\n---\\n${blocks}\\n---`;\n }\n }\n\n // Shared base: graph-derived neighborhood (connections / citedBy / siblings)\n // plus the LLM relationship summary, recomputed from the unified graph.\n if (mainResourceId) {\n const views = deriveViews(context.graph, mainResourceId, focalAnnotationId);\n const parts: string[] = [];\n\n if (views.connections.length > 0) {\n const connList = views.connections\n .map(c => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(', ')})` : ''} ${idLabel(c.resourceId)}`)\n .join(', ');\n parts.push(`- Connected resources: ${connList}`);\n }\n\n if (views.citedByCount > 0) {\n const citedNames = views.citedBy.map(c => `${c.resourceName} ${idLabel(c.resourceId)}`).join(', ');\n parts.push(`- This resource is cited by ${views.citedByCount} other resource${views.citedByCount > 1 ? 's' : ''}${citedNames ? `: ${citedNames}` : ''}`);\n }\n\n if (views.siblingEntityTypes.length > 0) {\n parts.push(`- Related entity types in this document: ${views.siblingEntityTypes.join(', ')}`);\n }\n\n if (context.inferredRelationshipSummary) {\n parts.push(`- Relationship summary: ${context.inferredRelationshipSummary}`);\n }\n\n if (parts.length > 0) {\n graphSection = `\\n\\nKnowledge graph context:\\n${parts.join('\\n')}`;\n }\n }\n }\n\n // Build semantic context section if available — the vector matches the gather\n // flow already retrieved for the focal passage, used to ground generation (RAG).\n // Capped and truncated to bound prompt cost (see the named caps above); the\n // gather step pre-filters to ≤10 matches above a 0.5 cosine threshold. Each\n // passage carries its source id so the model can attribute what it uses.\n // See .plans/SEMANTIC-CONTEXT-RAG.md + .plans/CONTEXT-IDENTIFIERS.md.\n let semanticContextSection = '';\n const similar = context?.semanticContext?.similar ?? [];\n if (similar.length > 0) {\n const lines = [...similar]\n .sort((a, b) => b.score - a.score)\n .slice(0, SEMANTIC_MATCH_LIMIT)\n .map(m => `- ${idLabel(m.resourceId, m.annotationId)} (${m.score.toFixed(2)})${m.machineRead ? ' [OCR]' : ''} ${m.text.slice(0, SEMANTIC_MATCH_CHARS)}`);\n // A passage marked [OCR] was recognized from a scanned image, so its\n // wording — and especially its digits — may be misread. Saying so is the\n // whole point of carrying the flag: the model is the only reader of these\n // passages, and it would otherwise quote a scanned figure as confidently\n // as a typed one.\n const ocrNote = similar.some(m => m.machineRead)\n ? '\\nPassages marked [OCR] were read from scanned images by character recognition; treat their exact wording and numbers as uncertain, and say so if you rely on one.'\n : '';\n semanticContextSection = `\\n\\nRelated passages from the knowledge base:\\n${lines.join('\\n')}${ocrNote}`;\n }\n\n // ── Task framing (YIELD-STRUCTURE D1): canonical tasks map to tested framings;\n // an unknown task string is used VERBATIM as the framing instruction (loud\n // degrade — warn, never a silent fallback to 'resource').\n let leadLine: string;\n if (task === 'resource') {\n leadLine = `Generate a concise, informative resource about \"${topic}\".`;\n } else if (task === 'answer') {\n leadLine = `Answer the following question directly and concisely, grounded in the provided context: \"${topic}\"`;\n } else if (task === 'summary') {\n leadLine = `Write a concise summary of \"${topic}\".`;\n } else {\n logger.warn('Unknown task — using it verbatim as the framing instruction', { task });\n leadLine = `${task}\\nTopic: \"${topic}\"`;\n }\n\n // ── Structure directive (YIELD-STRUCTURE D2/D4): emitted ONLY when the caller\n // sets one — unset means no directive at all (the task framing and the model\n // determine shape). Never derived from the token budget: maxTokens is length\n // only. The forced `# Title` heading exists only under canonical 'sections'.\n const isPlainText = outputMediaType === 'text/plain';\n // PDF output means the model authors Typst source (Q1, direct Typst); the\n // worker compiles it. The prompt therefore teaches Typst syntax, and the\n // markdown scaffolding never applies.\n const isPdf = outputMediaType === 'application/pdf';\n let structureRequirement = '';\n let titleRequirement = '';\n if (structure === 'sections') {\n structureRequirement = isPdf\n ? '\\n- Organize the content into titled sections (= Heading) with well-structured paragraphs'\n : isPlainText\n ? '\\n- Organize the content into titled sections with well-structured paragraphs'\n : '\\n- Organize the content into titled sections (## Section) with well-structured paragraphs';\n if (!isPlainText && !isPdf) {\n titleRequirement = '\\n- Start with a clear heading (# Title)';\n }\n } else if (structure === 'prose') {\n structureRequirement = '\\n- Write flowing, well-structured paragraphs with no section headings';\n } else if (structure === 'chat') {\n structureRequirement = '\\n- Structure the content as a conversational chat transcript — a sequence of alternating, speaker-labeled turns (no section headings)';\n } else if (structure) {\n logger.warn('Unknown structure — passing it through as freeform organization guidance', { structure });\n structureRequirement = `\\n- Organize the output as: ${structure}`;\n }\n\n // Citation instruction (INLINE-CITATIONS): ask the model to emit [[<id>]]\n // transport tokens next to each claim, citing only ids the context embedding\n // shows. The worker strips the tokens and reconciles them into linking\n // annotations — they never reach the stored content.\n const citeRequirement = cite\n ? '\\n- Ground every claim in the provided context. Immediately after each claim, cite its source by emitting [[<id>]], where <id> is an id shown in square brackets in the context above (for a passage labeled [abc], emit [[abc]]). Cite only ids that appear in the context.'\n : '';\n\n const formatRequirements = isPdf\n ? `- Write the response as Typst markup (the Typst typesetting language — not markdown, not LaTeX)\n- Headings are written as = Heading (deeper levels == Subheading); everything else is plain prose paragraphs\n- Do not emit markdown syntax or code fences`\n : isPlainText\n ? `- Write the response as plain text — no formatting markup (no #, *, backticks, headings, or links)\n- Begin with the title on its own first line`\n : `- Use markdown formatting\n- Write the response as markdown`;\n\n // Compile-repair context (PDF-GENERATION P3): the previous Typst source\n // failed to compile; the legible compiler diagnostics go back to the model\n // as an authoritative instruction to fix and re-emit the full document.\n const repairSection = repair\n ? `\\n\\nYour previous attempt failed to compile. Fix the error and return the complete corrected document — full source, not a diff.\nCompile error:\n${repair.error}\nPrevious source:\n${repair.source}`\n : '';\n\n // The caller's prompt is an authoritative leading Instruction (YIELD-STRUCTURE D3),\n // not background \"additional context\" — task = what to produce, prompt = how.\n const prompt = `${leadLine}\n${userPrompt ? `Instruction: ${userPrompt}` : ''}${repairSection}\n${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(', ')}.` : ''}${annotationSection}${contextSection}${resourceSection}${graphSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}\n\nRequirements:\n- Aim for approximately ${finalMaxTokens} tokens of content\n- Be factual and informative${structureRequirement}${titleRequirement}${citeRequirement}\n${formatRequirements}`;\n\n // Simple parser - just use the response directly as markdown\n const parseResponse = (response: string): { title: string; content: string } => {\n // Clean up any markdown code fences if present\n let content = response.trim();\n if (content.startsWith('```markdown') || content.startsWith('```md') || content.startsWith('```typst')) {\n content = content.slice(content.indexOf('\\n') + 1);\n const endIndex = content.lastIndexOf('```');\n if (endIndex !== -1) {\n content = content.slice(0, endIndex);\n }\n } else if (content.startsWith('```')) {\n content = content.slice(3);\n const endIndex = content.lastIndexOf('```');\n if (endIndex !== -1) {\n content = content.slice(0, endIndex);\n }\n }\n\n content = content.trim();\n\n // Title is provided by the caller (topic), not extracted from generated content\n return {\n title: topic,\n content: content\n };\n };\n\n logger.debug('Sending prompt to inference', {\n promptLength: prompt.length,\n temperature: finalTemperature,\n maxTokens: finalMaxTokens\n });\n const response = await boundedGenerateWithMetadata(client, prompt, finalMaxTokens, finalTemperature, undefined, logger);\n logger.debug('Got response from inference', { responseLength: response.text.length, stopReason: response.stopReason });\n\n const result = parseResponse(response.text);\n logger.debug('Parsed response', {\n hasTitle: !!result.title,\n titleLength: result.title?.length,\n hasContent: !!result.content,\n contentLength: result.content?.length\n });\n\n // The provider's stopReason collapses to one protocol-owned bit HERE and\n // nowhere else (GENERATE-FROM-RESOURCE D6): 'max_tokens' means the artifact\n // is cut off, not complete; every other reason is a natural stop.\n return { ...result, truncated: response.stopReason === 'max_tokens' };\n}\n","/**\n * Typst compiler invocation (PDF-GENERATION P3).\n *\n * The model writes Typst source (Q1, settled 2026-08-04: direct Typst); this\n * module turns it into PDF bytes via the pinned binary the worker image ships\n * (P2 — `/usr/local/bin/typst`, v0.15.1, resolved through PATH so tests can\n * substitute a fake). Compile failures return the legible error text — Typst's\n * `file:line:col` + caret diagnostics are the load-bearing input to the bounded\n * repair loop in `processGenerationJob`.\n */\nimport { execFileSync } from 'node:child_process';\nimport { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Every compile pins the PDF creation timestamp. Unpinned compiles of\n * identical source produce different bytes every time (CreationDate/ModDate),\n * which churns the content checksum and makes the Smelter re-embed a document\n * that did not change — the spurious-re-embed failure S11/S12 exist to\n * prevent. The value is arbitrary; its fixity is the point.\n */\nexport const PINNED_CREATION_TIMESTAMP = 1700000000;\n\n/**\n * Model-authored source can fail to compile; the error is fed back for repair\n * a bounded number of times, then the job fails loudly with the compiler's\n * diagnostics. Bounded because generation is non-idempotent and paid per\n * attempt — this is a repair loop, not a retry loop.\n */\nexport const MAX_COMPILE_REPAIRS = 2;\n\nexport function compileTypst(source: string): { pdf: Uint8Array } | { error: string } {\n const dir = mkdtempSync(join(tmpdir(), 'typst-'));\n try {\n const inFile = join(dir, 'doc.typ');\n const outFile = join(dir, 'doc.pdf');\n writeFileSync(inFile, source);\n try {\n execFileSync(\n 'typst',\n ['compile', '--creation-timestamp', String(PINNED_CREATION_TIMESTAMP), inFile, outFile],\n { stdio: ['ignore', 'pipe', 'pipe'] },\n );\n } catch (err) {\n const stderr = (err as { stderr?: Buffer }).stderr;\n return { error: stderr?.length ? stderr.toString('utf8') : String(err) };\n }\n return { pdf: new Uint8Array(readFileSync(outFile)) };\n } finally {\n rmSync(dir, { recursive: true, force: true });\n }\n}\n","/**\n * Citation-token resolver (INLINE-CITATIONS P1).\n *\n * Under `cite`, the generation prompt asks the model to emit `[[<resourceId>]]` /\n * `[[<resourceId>/<annotationId>]]` transport tokens after each claim, citing ids\n * the context embedding put in front of it (CONTEXT-IDENTIFIERS). Tokens are\n * TRANSPORT, not content: this resolver parses them, validates each id against the\n * ids actually present in the embedded context (the hallucination guard — an\n * unknown id is dropped loudly, never silently linked), STRIPS them from the\n * content before upload, and returns claim-span citations the worker mints as W3C\n * linking annotations on the derived resource. See .plans/INLINE-CITATIONS.md.\n */\nimport type { GatheredContext, Logger } from '@semiont/core';\n\nexport interface GenerationCitation {\n /** The cited source resource (validated present in the embedded context). */\n resourceId: string;\n /** The contributing annotation, when the cited excerpt was annotation-derived. */\n annotationId?: string;\n /** Claim span in the FINAL (token-stripped) content; substring(start, end) === exact. */\n start: number;\n end: number;\n exact: string;\n}\n\n/** `[[rid]]` or `[[rid/annId]]` — ids are bare (no whitespace, brackets, or slashes). */\nconst CITATION_TOKEN = /\\[\\[([^\\s[\\]/]+)(?:\\/([^\\s[\\]/]+))?\\]\\]/g;\n\n/**\n * The ids the context embedding actually put in front of the model — the\n * hallucination guard's ground truth. Mirrors what the prompt renderers label:\n * the focal resource, graph resource nodes, semantic matches, related content.\n */\nexport function collectContextResourceIds(context: GatheredContext | undefined): Set<string> {\n const ids = new Set<string>();\n if (!context) return ids;\n\n const { focus } = context;\n ids.add(focus.kind === 'annotation' ? focus.sourceResource['@id'] : focus.resource['@id']);\n for (const node of context.graph.nodes) {\n if (node.type === 'resource') ids.add(node.id);\n }\n for (const m of context.semanticContext?.similar ?? []) ids.add(m.resourceId);\n if (focus.kind === 'resource') {\n for (const id of Object.keys(focus.content?.related ?? {})) ids.add(id);\n }\n return ids;\n}\n\nexport function resolveCitationTokens(\n content: string,\n validResourceIds: ReadonlySet<string>,\n logger: Logger,\n): { content: string; citations: GenerationCitation[] } {\n const citations: GenerationCitation[] = [];\n let clean = '';\n let last = 0;\n\n for (const match of content.matchAll(CITATION_TOKEN)) {\n const token = match[0];\n const citedResourceId = match[1]!;\n const annotationId = match[2];\n\n // Append the text before the token, dropping the whitespace run immediately\n // preceding it so stripping never leaves a dangling space.\n clean += content.slice(last, match.index).replace(/[ \\t]+$/, '');\n last = match.index! + token.length;\n\n if (!validResourceIds.has(citedResourceId)) {\n logger.warn('Citation token references an id absent from the provided context — dropped', {\n resourceId: citedResourceId,\n });\n continue;\n }\n\n // Claim span: the sentence preceding the token, computed against the clean\n // content. Offsets are final — later appends never shift earlier positions.\n const end = clean.length;\n let start = 0;\n for (let i = end - 2; i >= 0; i--) {\n const ch = clean[i];\n if (ch === '.' || ch === '!' || ch === '?' || ch === '\\n') {\n start = i + 1;\n break;\n }\n }\n while (start < end && /\\s/.test(clean[start]!)) start++;\n const exact = clean.slice(start, end);\n if (exact.length === 0) {\n logger.warn('Citation token has no preceding claim text — dropped', {\n resourceId: citedResourceId,\n });\n continue;\n }\n\n citations.push({\n resourceId: citedResourceId,\n ...(annotationId ? { annotationId } : {}),\n start,\n end,\n exact,\n });\n }\n\n clean += content.slice(last);\n return { content: clean, citations };\n}\n","/**\n * Run `worker` over `items` with at most `limit` of them in flight at once\n * (DETECTION-QUALITY-THROUGHPUT P6). Hand-rolled rather than a dependency: the\n * need is exactly one bounded map, and the BOUND is the whole point — detection\n * parallelizes independent entity types, but unbounded fan-out just trades\n * sequential waiting for provider 429 thrash, so the cap is the feature.\n *\n * A fixed pool of `min(limit, items.length)` pumps, each pulling the next index\n * until the work is exhausted — so at most `limit` calls are ever awaiting at\n * once, whatever the item count. Results are returned in INPUT order (a caller\n * can zip them back to items); COMPLETION order is not input order — that is the\n * point of concurrency — so a caller that must act \"as each finishes\" does so\n * inside `worker`, not on the returned array.\n *\n * Failure matches the sequential loop it replaces: the first `worker` rejection\n * rejects the whole run. Pumps already awaiting finish (they cannot be\n * cancelled mid-await), but no new item is pulled once a rejection propagates.\n */\nexport async function runBounded<T, R>(\n items: readonly T[],\n limit: number,\n worker: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let next = 0;\n\n async function pump(): Promise<void> {\n while (true) {\n const i = next++;\n if (i >= items.length) return;\n results[i] = await worker(items[i]!, i);\n }\n }\n\n const poolSize = Math.max(1, Math.min(limit, items.length));\n await Promise.all(Array.from({ length: poolSize }, () => pump()));\n return results;\n}\n","/**\n * Job Processors\n *\n * Pure functions that take content + inference client + params,\n * report progress via callback, and return annotations + results.\n *\n * No EventBus, no JobQueue, no side effects except calling inference.\n * Driven by the remote worker process (worker-process.ts), which claims\n * jobs over SSE and dispatches by jobType to these functions.\n */\n\nimport { AnnotationDetection } from './workers/annotation-detection';\nimport { extractEntities } from './workers/detection/entity-extractor';\nimport { DEFAULT_MAX_TOKENS, generateResourceFromTopic } from './workers/generation/resource-generation';\nimport { compileTypst, MAX_COMPILE_REPAIRS } from './workers/generation/typst-compiler';\nimport { withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';\nimport { resolveCitationTokens, collectContextResourceIds, type GenerationCitation } from './workers/generation/citation-resolver';\nimport { annotationIdFor } from '@semiont/event-sourcing';\nimport { didToAgent, GENERATABLE_MEDIA_TYPES, type Annotation, type GenerationJobParams, type Logger, type ResourceId, type SupportedMediaType, type components } from '@semiont/core';\nimport { reconcileSelector, createFragmentSelector, locate, type ReconciledSelector, type AnchoredText } from '@semiont/core';\nimport type { InferenceClient } from '@semiont/inference';\nimport type {\n HighlightDetectionParams,\n CommentDetectionParams,\n AssessmentDetectionParams,\n DetectionParams,\n TagDetectionParams,\n HighlightDetectionResult,\n CommentDetectionResult,\n AssessmentDetectionResult,\n DetectionResult,\n TagDetectionResult,\n GenerationResult,\n} from './types';\nimport { noteAnchor } from './workers/detection/anchor-audit';\nimport { runBounded } from './workers/detection/bounded-concurrency';\n\ntype Agent = components['schemas']['Agent'];\n\n/** A detected span — offsets into the extracted `.text`, plus optional context. */\nexport type SpanMatch = { exact: string; start: number; end: number; prefix?: string; suffix?: string };\n\n/**\n * The span half of an annotation's identity (JOB-RESTART-SAFETY P3).\n *\n * Shared by both builders because the span IS the same fact in both — the PDF\n * path additionally persists geometry for it, but that geometry is derived\n * from these offsets, so hashing it too would add no distinguishing power and\n * would make an id depend on a layout the text path cannot reproduce.\n *\n * `exact` is included, not just the offsets: after a content update the same\n * offsets cover different text, and that is a different annotation.\n */\nfunction spanAnchor(match: Pick<SpanMatch, 'start' | 'end' | 'exact'>): string {\n return `${match.start}:${match.end}:${match.exact}`;\n}\n\n/**\n * Turn a detected span into a stored annotation. The media type, resource, and\n * attribution context are closed over by the caller (see `prepareDetection`);\n * the detection processor supplies only the motivation, the span, and any\n * motivation-specific body. This is the single axis that varies by media type,\n * so the detection processors themselves stay media-agnostic.\n */\nexport type BuildAnnotation = (\n motivation: Motivation,\n match: SpanMatch,\n body?: Annotation['body'],\n) => Annotation;\n\n/**\n * Progress callback. The two positional args are the required `JobProgress`\n * fields (`percentage`, `message`). The third optional arg carries the\n * job-type-specific fields (`completedEntityTypes`, `requestParams`, etc.)\n * that the progress UI renders.\n *\n * Anything in `extra` describing the RUN rather than the moment must be passed\n * on EVERY call: the client's `progress$` replaces its value per event, so a\n * field sent once disappears on the next tick.\n *\n * `message` is a CODE plus typed params, never a prose sentence\n * (ASSIST-PROGRESS-CONSOLIDATION A6). The producer reports what happened;\n * each client renders it in the user's language — react-ui from its 29\n * locales, the Go launcher from its English map. The vocabulary is frozen\n * by the census of these call sites: adding a shape means adding a variant\n * to `JobProgressMessage.json` and copy in every client, not composing a\n * new sentence here.\n */\nexport type OnProgress = (\n percentage: number,\n message: JobProgressMessage,\n extra?: Partial<JobProgress>,\n) => void;\n\ntype JobProgress = components['schemas']['JobProgress'];\n/** Derived from the wire, never restated: the per-unit entry's shape is the\n * spec's, so a field added there reaches these accumulators by regeneration\n * rather than by someone remembering two places. */\ntype CompletedItem = NonNullable<JobProgress['completedItems']>[number];\ntype JobProgressMessage = components['schemas']['JobProgressMessage'];\n\n/** The five W3C motivations this system mints — a closed vocabulary, so it is\n * typed as one rather than as `string`. */\nexport type Motivation = Annotation['motivation'];\n\nexport interface ProcessorResult<R> {\n annotations: Annotation[];\n result: R;\n}\n\n/**\n * Strip the audit-only fields (`anchorMethod`, `llmOffsets`, `matchQuality`)\n * off a `ReconciledSelector` so the rest is shaped like a match input for\n * `buildTextAnnotation`. The audit info belongs in logs, not in storage.\n */\nfunction toMatch(r: ReconciledSelector): { exact: string; start: number; end: number; prefix?: string; suffix?: string } {\n return {\n exact: r.exact,\n start: r.start,\n end: r.end,\n ...(r.prefix !== undefined ? { prefix: r.prefix } : {}),\n ...(r.suffix !== undefined ? { suffix: r.suffix } : {}),\n };\n}\n\n/**\n * Identity key for a built annotation: motivation + anchored span + body.\n * Two annotations with the same key are the same event written twice.\n */\nfunction annotationDedupeKey(ann: Record<string, unknown>): string {\n const target = ann.target as\n | { selector?: Array<{ type: string; start?: number; end?: number; value?: string; exact?: string; prefix?: string; suffix?: string }> }\n | undefined;\n const selectors = Array.isArray(target?.selector) ? target.selector : [];\n const pos = selectors.find((s) => s.type === 'TextPositionSelector');\n // Anchor identity is media-specific. Text annotations carry a\n // TextPositionSelector (durable char offsets). PDF annotations have none —\n // their anchor is the per-line FragmentSelector viewrect geometry plus the\n // TextQuoteSelector text. Keying only on TextPositionSelector would collapse\n // every PDF annotation sharing a motivation+body onto one (its offsets fall\n // back to '?'), so e.g. multiple PDF highlights emit as a single annotation.\n let anchor: string;\n if (pos) {\n anchor = `pos:${pos.start ?? '?'}:${pos.end ?? '?'}`;\n } else {\n const frags = selectors.filter((s) => s.type === 'FragmentSelector').map((s) => s.value ?? '').join(',');\n const quote = selectors.find((s) => s.type === 'TextQuoteSelector');\n anchor = `frag:${frags}|quote:${quote?.exact ?? ''}:${quote?.prefix ?? ''}:${quote?.suffix ?? ''}`;\n }\n return [ann.motivation as string, anchor, JSON.stringify(ann.body ?? null)].join('|');\n}\n\n/**\n * Drop annotations that are identical in the fields that define an\n * annotation's meaning: motivation, anchored span, and body.\n *\n * Why this is needed: each LLM-emitted span is reconciled independently\n * (no cross-entry coordination), and `reconcileSelector`'s `first-of-many`\n * fallback anchors every undisambiguated entry at the *same* first\n * occurrence. So a phrase repeated in non-distinctive context can produce\n * several entries that all collapse onto one span — identical events. This\n * collapses them back to one.\n *\n * What it does NOT drop: same span, *different* body (e.g. the same text\n * tagged as two entity types, or two distinct comments on one passage).\n * Those are legitimately distinct annotations.\n *\n * Applied identically by every processor below.\n */\nfunction dedupeAnnotations(annotations: Annotation[]): Annotation[] {\n const seen = new Set<string>();\n const out: Annotation[] = [];\n for (const ann of annotations) {\n const key = annotationDedupeKey(ann);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(ann);\n }\n return out;\n}\n\nexport function buildTextAnnotation(\n content: string,\n resourceId: ResourceId,\n userId: string,\n generator: Agent,\n motivation: Motivation,\n match: { exact: string; start: number; end: number; prefix?: string; suffix?: string },\n // Body may be a single AnnotationBody object or a non-empty array of\n // them, OR omitted entirely. W3C treats body as optional; annotations\n // whose motivation alone conveys meaning (highlighting) legitimately\n // skip it. Every other motivation currently passes something; the\n // processor that calls this makes the choice per-motivation.\n body?: Annotation['body'],\n) {\n // Write-time invariant. Every selector that reaches storage must be\n // internally consistent with the source content. If a worker bypasses\n // `reconcileSelector` or a future change re-introduces overlap, the\n // throw fires loudly here instead of corrupting the KB.\n if (content.substring(match.start, match.end) !== match.exact) {\n throw new Error(\n `buildTextAnnotation invariant: content.substring(${match.start}, ${match.end}) !== exact ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n if (match.prefix !== undefined) {\n const actualPrefix = content.substring(Math.max(0, match.start - match.prefix.length), match.start);\n if (actualPrefix !== match.prefix) {\n throw new Error(\n `buildTextAnnotation invariant: content prefix-slice !== prefix ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n }\n if (match.suffix !== undefined) {\n const actualSuffix = content.substring(match.end, Math.min(content.length, match.end + match.suffix.length));\n if (actualSuffix !== match.suffix) {\n throw new Error(\n `buildTextAnnotation invariant: content suffix-slice !== suffix ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n }\n\n // `userId` here is the DID of the human who initiated the work. The\n // worker process is acting on their behalf using `generator` to\n // produce content. Per the protocol attribution model:\n // creator = who initiated (the human)\n // generator = what produced (the software peer)\n // wasAttributedTo = both parties (PROV-O)\n // For autonomous-agent work creator and generator collapse to the\n // same Software Agent; the same field assignments still hold.\n const creator = didToAgent(userId);\n const wasAttributedTo: Agent[] =\n creator['@id'] === generator['@id'] ? [generator] : [creator, generator];\n return {\n '@context': 'http://www.w3.org/ns/anno.jsonld' as const,\n 'type': 'Annotation' as const,\n 'id': annotationIdFor({ resourceId: resourceId as string, motivation, anchor: spanAnchor(match), body }),\n motivation,\n creator,\n generator,\n wasAttributedTo,\n created: new Date().toISOString(),\n target: {\n type: 'SpecificResource' as const,\n source: resourceId as string,\n selector: [\n { type: 'TextPositionSelector' as const, start: match.start, end: match.end },\n {\n type: 'TextQuoteSelector' as const,\n exact: match.exact,\n ...(match.prefix && { prefix: match.prefix }),\n ...(match.suffix && { suffix: match.suffix }),\n },\n ],\n },\n ...(body !== undefined ? { body } : {}),\n };\n}\n\n/**\n * PDF sibling of `buildTextAnnotation`. The model returns the same\n * `{ exact, start, end, prefix?, suffix? }` match over the extracted text\n * layer's `text`; geometry comes from the layer, never the model.\n *\n * `target.selector` = one `FragmentSelector` per line (`locate` unions the\n * overlapping text-layer items into per-line viewrects) plus a\n * `TextQuoteSelector` anchor. No `TextPositionSelector`: the extracted text\n * layer is a derived artifact, not the stored content, so its char offsets are\n * not a durable anchor.\n *\n * Write-time invariant (geometry <-> text): geometry is item-level (word runs),\n * so the covered items' text must *contain* `exact` (whitespace-normalized) —\n * containment, not reconstruction. An empty cover (no overlapping items -> no\n * rects) also fails. Throws loudly, naming the resource + motivation, rather\n * than persisting geometry that doesn't back the quoted text.\n */\nexport function buildPdfAnnotation(\n anchored: AnchoredText,\n resourceId: ResourceId,\n userId: string,\n generator: Agent,\n motivation: Motivation,\n match: { exact: string; start: number; end: number; prefix?: string; suffix?: string },\n body?: Annotation['body'],\n) {\n // `locate` returns both the per-line rects and the overlap items it found;\n // reuse `overlap` for the containment check rather than re-scanning layer.items.\n const { rects, overlap } = locate(anchored, match.start, match.end);\n\n const coveredText = overlap.length\n ? anchored.text.substring(\n Math.min(...overlap.map((i) => i.start)),\n Math.max(...overlap.map((i) => i.end)),\n )\n : '';\n const normalize = (s: string) => s.replace(/\\s+/g, ' ').trim();\n if (rects.length === 0 || !normalize(coveredText).includes(normalize(match.exact))) {\n throw new Error(\n `buildPdfAnnotation invariant: covered text does not contain exact ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n\n const creator = didToAgent(userId);\n const wasAttributedTo: Agent[] =\n creator['@id'] === generator['@id'] ? [generator] : [creator, generator];\n\n return {\n '@context': 'http://www.w3.org/ns/anno.jsonld' as const,\n 'type': 'Annotation' as const,\n 'id': annotationIdFor({ resourceId: resourceId as string, motivation, anchor: spanAnchor(match), body }),\n motivation,\n creator,\n generator,\n wasAttributedTo,\n created: new Date().toISOString(),\n target: {\n type: 'SpecificResource' as const,\n source: resourceId as string,\n selector: [\n ...rects.map((coord) => ({\n type: 'FragmentSelector' as const,\n conformsTo: 'http://tools.ietf.org/rfc/rfc3778' as const,\n value: createFragmentSelector(coord),\n })),\n {\n type: 'TextQuoteSelector' as const,\n exact: match.exact,\n ...(match.prefix && { prefix: match.prefix }),\n ...(match.suffix && { suffix: match.suffix }),\n },\n ],\n },\n ...(body !== undefined ? { body } : {}),\n };\n}\n\nexport async function processHighlightJob(\n content: string,\n inferenceClient: InferenceClient,\n params: HighlightDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<HighlightDetectionResult>> {\n const echo = detectionEcho(params);\n\n onProgress(10, { code: 'loading' }, echo);\n onProgress(30, { code: 'analyzing' }, echo);\n\n const highlights = await AnnotationDetection.detectHighlights(\n content, inferenceClient, params.instructions, params.density, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.\n (completed, total) => onProgress(30 + Math.round((completed / total) * 30), { code: 'analyzing' }, echo),\n );\n\n onProgress(60, { code: 'creating-annotations', count: highlights.length }, echo);\n\n // Highlights carry no body — motivation:'highlighting' on a target\n // is a complete annotation per the W3C Web Annotation Model.\n const annotations = dedupeAnnotations(highlights.map((h) =>\n buildAnnotation('highlighting', h),\n ));\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'highlight' }, echo);\n\n return {\n annotations,\n result: { kind: 'highlight-annotation', highlightsFound: highlights.length, highlightsCreated: annotations.length },\n };\n}\n\n/**\n * The user's own inputs, echoed back for the progress widget. Labels are CODES\n * (the client localizes them); values are the user's words and are shown\n * verbatim. Absent or blank inputs are omitted rather than rendered as empty\n * rows — \"Instructions:\" with nothing after it is noise, not information.\n *\n * Returned as the `extra` object rather than a bare array because every\n * `onProgress` in the run passes it: `progress$` REPLACES its value per event\n * (mark-state-unit), so a field sent once would flash at 10% and disappear.\n * The parameters describe the whole run, so every event carries them — the\n * same convention `processReferenceJob` already follows for its entity types.\n */\nfunction detectionEcho(p: {\n instructions?: string;\n tone?: string;\n density?: number;\n}): Partial<JobProgress> {\n const requestParams: Array<{ label: 'instructions' | 'tone' | 'density'; value: string }> = [];\n if (p.instructions?.trim()) requestParams.push({ label: 'instructions', value: p.instructions.trim() });\n if (p.tone?.trim()) requestParams.push({ label: 'tone', value: p.tone.trim() });\n if (p.density !== undefined) requestParams.push({ label: 'density', value: String(p.density) });\n return requestParams.length > 0 ? { requestParams } : {};\n}\n\nexport async function processCommentJob(\n content: string,\n inferenceClient: InferenceClient,\n params: CommentDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<CommentDetectionResult>> {\n const echo = detectionEcho(params);\n\n onProgress(10, { code: 'loading' }, echo);\n onProgress(30, { code: 'analyzing' }, echo);\n\n const comments = await AnnotationDetection.detectComments(\n content, inferenceClient, params.instructions, params.tone, params.density,\n params.language, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.\n (completed, total) => onProgress(30 + Math.round((completed / total) * 30), { code: 'analyzing' }, echo),\n );\n\n onProgress(60, { code: 'creating-annotations', count: comments.length }, echo);\n\n // The body's `language` reflects the locale the LLM was asked to write in\n // (`params.language` — the user's UI locale). Defaults to 'en' when the\n // caller didn't specify, matching what the LLM produces by default.\n const bodyLanguage = params.language ?? 'en';\n const annotations = dedupeAnnotations(comments.map((c) =>\n // Match the pre-#651 CommentAnnotationWorker: include format and\n // language on the body TextualBody. Optional in the schema, but\n // consumers that do language-aware rendering rely on them.\n buildAnnotation('commenting', c, [\n { type: 'TextualBody', value: c.comment, purpose: 'commenting', format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage },\n ]),\n ));\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'comment' }, echo);\n\n return {\n annotations,\n result: { kind: 'comment-annotation', commentsFound: comments.length, commentsCreated: annotations.length },\n };\n}\n\nexport async function processAssessmentJob(\n content: string,\n inferenceClient: InferenceClient,\n params: AssessmentDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<AssessmentDetectionResult>> {\n const echo = detectionEcho(params);\n\n onProgress(10, { code: 'loading' }, echo);\n onProgress(30, { code: 'analyzing' }, echo);\n\n const assessments = await AnnotationDetection.detectAssessments(\n content, inferenceClient, params.instructions, params.tone, params.density,\n params.language, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.\n (completed, total) => onProgress(30 + Math.round((completed / total) * 30), { code: 'analyzing' }, echo),\n );\n\n onProgress(60, { code: 'creating-annotations', count: assessments.length }, echo);\n\n const bodyLanguage = params.language ?? 'en';\n const annotations = dedupeAnnotations(assessments.map((a) =>\n // Single-object body with purpose aligned to motivation, matching the\n // pre-#651 AssessmentAnnotationWorker's shape and the majority of\n // persisted assessments. Do not switch to an array or to\n // purpose='describing' — that loses the \"this is an assessment, not\n // a description\" signal and breaks existing readers that access\n // `body.value` directly on the object.\n buildAnnotation('assessing', a, {\n type: 'TextualBody', value: a.assessment, purpose: 'assessing', format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage,\n }),\n ));\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'assessment' }, echo);\n\n return {\n annotations,\n result: { kind: 'assessment-annotation', assessmentsFound: assessments.length, assessmentsCreated: annotations.length },\n };\n}\n\n/**\n * Reference detection commits per UNIT — one entity type — through\n * `onUnitComplete` (ABANDONED-INFERENCE P2, checkpointed resume): the\n * callback receives the unit's deduped annotations, and only after it\n * resolves does the unit count as complete. Emission belongs to the\n * callback alone; the processor returns only the result — returning the\n * annotations as well would recreate the post-run batch that N2 showed\n * discards completed work wholesale.\n */\nexport async function processReferenceJob(\n content: string,\n inferenceClient: InferenceClient,\n params: DetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n logger: Logger,\n onUnitComplete: (entityType: string, annotations: Annotation[]) => Promise<void>,\n signal?: AbortSignal,\n): Promise<{ result: DetectionResult }> {\n const entityTypeNames = params.entityTypes.map(String);\n const requestParams = [{ label: 'entity-types' as const, value: entityTypeNames.join(', ') }];\n const completedItems: CompletedItem[] = [];\n let totalFound = 0;\n let totalEmitted = 0;\n let errors = 0;\n\n onProgress(10, { code: 'loading' }, { requestParams });\n\n const bodyLanguage = params.language ?? 'en';\n\n // Entity types run BOUNDED-CONCURRENT (DETECTION-QUALITY-THROUGHPUT P6).\n // They are independent units — own extraction, own commit, own checkpoint —\n // and a single sequential job used a sliver of the provider's rate limit, so\n // the old `for … await` was the 9×-sequential ≈ 2.5 h/document. The bound is\n // the point: unbounded fan-out just trades sequential waiting for 429 thrash.\n //\n // Shared counters and `completedItems` are mutated SYNCHRONOUSLY between\n // awaits inside the worker — safe under the single-threaded event loop (no\n // read-modify-write straddles an await), so no locking is needed. Progress is\n // now \"M of N done\" rather than \"on type i\": concurrent types finish out of\n // order, and `completedItems` already tolerates that.\n let completed = 0;\n const total = entityTypeNames.length;\n\n const emitTypeProgress = (entityTypeName: string): void => {\n const pct = 20 + Math.round((completed / total) * 60);\n onProgress(pct, { code: 'detecting-entities', entityType: entityTypeName }, {\n current: { kind: 'entity-type', value: entityTypeName },\n processed: completed,\n total,\n entitiesFound: totalFound,\n entitiesEmitted: totalEmitted,\n completedItems: [...completedItems],\n requestParams,\n });\n };\n\n // Concurrency is the PROVIDER's capability, not a flat constant: a hosted\n // API parallelizes, a local single-model server does not (P6). Reading it\n // from the client is what makes the same code correct on both.\n await runBounded(entityTypeNames, inferenceClient.maxConcurrency, async (entityTypeName) => {\n if (!entityTypeName) return;\n // Cooperative cancellation (JOB-RESTART-SAFETY P4): once aborted, a pending\n // type is skipped when its turn comes; types already in flight finish and\n // commit, so committed units stay checkpointed. The caller reads\n // `signal.aborted` to move the job to cancelled/ rather than complete/.\n if (signal?.aborted) return;\n\n emitTypeProgress(entityTypeName);\n\n const extractedEntities = await extractEntities(\n content, [entityTypeName], inferenceClient, params.includeDescriptiveReferences ?? false, logger,\n params.sourceLanguage,\n // Liveness heartbeat (DETECTION-HEARTBEAT): fires at chunk boundaries and\n // every ~15 s while a call is in flight, so a long single-chunk call is\n // not silent. It repeats the current position rather than inventing an\n // advance — the stall watchdog, janitor and client timeout need a signal,\n // not a monotone.\n () => emitTypeProgress(entityTypeName),\n );\n\n // Unresolved reference body: the entity type as a tagging TextualBody,\n // stamped with the body locale to match the comment/assess/tag pattern.\n // The bind flow later appends a SpecificResource (purpose: 'linking') via\n // mark:body-updated to produce the resolved shape. Emitting an empty body\n // would break the append contract.\n const unresolvedBody: Annotation['body'] = [\n { type: 'TextualBody' as const, value: entityTypeName, purpose: 'tagging' as const, format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage },\n ];\n\n const built: Annotation[] = [];\n for (const entity of extractedEntities) {\n const reconciled = reconcileSelector(content, {\n exact: entity.exact,\n ...(entity.prefix !== undefined ? { prefix: entity.prefix } : {}),\n ...(entity.suffix !== undefined ? { suffix: entity.suffix } : {}),\n });\n if (!reconciled) {\n logger.error('Entity dropped — text not found in source', {\n text: entity.exact,\n entityType: entity.entityType,\n });\n errors++;\n continue;\n }\n noteAnchor('reference', entity.exact, reconciled.anchorMethod, logger);\n const ann = buildAnnotation('linking', toMatch(reconciled), unresolvedBody);\n built.push(ann);\n }\n\n // De-dupe within the unit, then COMMIT: the awaited callback emits the\n // unit's annotations, and only after it resolves does the unit count\n // anywhere — a unit whose commit fails contributes nothing (unit\n // atomicity, A3).\n const unitAnnotations = dedupeAnnotations(built);\n await onUnitComplete(entityTypeName, unitAnnotations);\n totalEmitted += unitAnnotations.length;\n totalFound += extractedEntities.length;\n // Found vs persisted, per unit: the model proposed `extractedEntities`, the\n // log holds `unitAnnotations` after dedupe and the commit ack. The gap\n // between them is this flow's yield (DETECTION-QUALITY-THROUGHPUT P1).\n completedItems.push({\n value: entityTypeName,\n foundCount: extractedEntities.length,\n persistedCount: unitAnnotations.length,\n });\n completed++;\n emitTypeProgress(entityTypeName);\n });\n\n // The terminal frame carries the completed set, and it is the only frame\n // that can: the per-unit entries ride the progress emitted at the START of\n // each unit, so the LAST unit's entry — and on a single-type job, every\n // entry — was never reported anywhere (DETECTION-QUALITY-THROUGHPUT P1).\n onProgress(100, { code: 'complete-created', count: totalEmitted, kind: 'reference' }, {\n completedItems: [...completedItems],\n requestParams,\n });\n\n return {\n result: { kind: 'reference-annotation', totalFound, totalEmitted, errors },\n };\n}\n\nexport async function processTagJob(\n content: string,\n inferenceClient: InferenceClient,\n params: TagDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<TagDetectionResult>> {\n onProgress(10, { code: 'loading' });\n onProgress(30, { code: 'analyzing-tags' });\n\n const allTags = [];\n const completedItems: CompletedItem[] = [];\n for (let c = 0; c < params.categories.length; c++) {\n const category = params.categories[c]!;\n // The loop always existed; it just never reported itself, so the tag flow\n // was the one counting flow with no subject line (CLEAN-PROGRESS A5).\n const position = () => ({\n current: { kind: 'category' as const, value: category },\n processed: c,\n total: params.categories.length,\n completedItems: [...completedItems],\n });\n onProgress(\n 30 + Math.round((c / params.categories.length) * 30),\n { code: 'analyzing-tags' },\n position(),\n );\n const categoryTags = await AnnotationDetection.detectTags(\n content, inferenceClient, params.schema, category, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): this category's\n // slice of the 30–60 band.\n (completed, total) => onProgress(\n 30 + Math.round(((c + completed / total) / params.categories.length) * 30),\n { code: 'analyzing-tags' },\n position(),\n ),\n );\n completedItems.push({ value: category, foundCount: categoryTags.length });\n allTags.push(...categoryTags);\n }\n const tags = allTags;\n\n onProgress(60, { code: 'creating-tag-annotations', count: tags.length });\n\n const bodyLanguage = params.language ?? 'en';\n const annotations = dedupeAnnotations(tags.map((t) => {\n const category = t.category ?? 'unknown';\n // Two-body shape matches the pre-#651 TagAnnotationWorker and every\n // persisted tag annotation: the category as a tagging TextualBody,\n // plus the tagging-schema id as a classifying TextualBody. The\n // classifying body is the only trace of schema provenance in the\n // event log — do not drop it.\n return buildAnnotation('tagging', t, [\n { type: 'TextualBody', value: category, purpose: 'tagging', format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage },\n { type: 'TextualBody', value: params.schema.id, purpose: 'classifying', format: 'text/plain' satisfies SupportedMediaType },\n ]);\n }));\n\n // byCategory is computed from the *deduped* set so the per-category\n // counts match what's actually stored. The category is the first\n // (tagging) TextualBody's value.\n const byCategory: Record<string, number> = {};\n for (const ann of annotations) {\n const body = (ann as { body?: Array<{ value?: unknown }> }).body;\n const category = Array.isArray(body) && typeof body[0]?.value === 'string' ? body[0].value : 'unknown';\n byCategory[category] = (byCategory[category] ?? 0) + 1;\n }\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'tag' });\n\n return {\n annotations,\n result: { kind: 'tag-annotation', tagsFound: tags.length, tagsCreated: annotations.length, byCategory },\n };\n}\n\n/**\n * Output bound (PDF-GENERATION P5), symmetric with #1124's extraction budget\n * and deliberately the SAME threshold: an artifact larger than what extraction\n * accepts would be a resource our own Smelter declines as 'too-large'. One\n * judgment, two enforcement points. A runaway generation fails loudly\n * (job:fail); it never uploads.\n */\nexport function assertWithinOutputBudget(byteLength: number): void {\n if (!withinByteBudget(byteLength)) {\n throw new Error(\n `Generated artifact exceeds the output byte budget: ${byteLength} bytes > ${MAX_PDF_BYTES}. Refusing a runaway generation.`,\n );\n }\n}\n\nexport async function processGenerationJob(\n inferenceClient: InferenceClient,\n params: GenerationJobParams,\n onProgress: OnProgress,\n logger: Logger,\n): Promise<{ content: Uint8Array; title: string; format: SupportedMediaType; citations: GenerationCitation[]; result: GenerationResult }> {\n // Refuse any requested media type the registry doesn't mark `generatable` —\n // loudly (the throw propagates as job:fail), never a silent markdown fallback\n // under a mislabeled format. The gate reads the registry capability\n // (PDF-GENERATION P1), not a local table. Validate before the LLM call.\n const outputMediaType: SupportedMediaType = params.outputMediaType ?? 'text/markdown';\n if (!GENERATABLE_MEDIA_TYPES.includes(outputMediaType)) {\n throw new Error(\n `Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(' or ')}.`,\n );\n }\n\n const title = params.title ?? 'Untitled';\n const entityTypes = (params.entityTypes ?? []).map(String);\n\n // PDF path (PDF-GENERATION P3): the model authors Typst; the worker's pinned\n // binary compiles it, with the legible compile errors fed back for a bounded\n // number of repairs. Citations on PDFs need page geometry, not text offsets —\n // until the citation branch (P4) provides it, `cite` fails fast and loudly\n // rather than minting selectors that would render nothing.\n if (outputMediaType === 'application/pdf') {\n onProgress(5, { code: 'generating-resource' });\n\n // Under `cite`, [[<id>]] tokens are stripped from the SOURCE before every\n // compile — they must never render into the artifact. The citations carry\n // the claim text; the worker re-anchors it by page geometry after\n // extraction (P4). Offsets in these citations index the Typst source and\n // are NOT used for PDF anchoring.\n const validIds = params.cite === true ? collectContextResourceIds(params.context) : null;\n\n let generated = await generateResourceFromTopic(\n title, entityTypes, inferenceClient, logger,\n params.prompt, params.language, params.context, params.temperature,\n params.maxTokens, params.sourceLanguage, outputMediaType,\n params.task, params.structure, params.cite,\n );\n let source = generated.content;\n let citations: GenerationCitation[] = [];\n if (validIds) {\n const resolved = resolveCitationTokens(generated.content, validIds, logger);\n source = resolved.content;\n citations = resolved.citations;\n }\n let compiled = compileTypst(source);\n let repairs = 0;\n while ('error' in compiled) {\n // A truncated source that fails to compile is cut off, not wrong —\n // every repair regenerates under the same ceiling and cannot restore\n // content that was never generated. Fail immediately with the actual\n // cause instead of burning the repair budget and blaming the compiler\n // (GENERATE-FROM-RESOURCE P3a).\n if (generated.truncated) {\n throw new Error(\n `Generation stopped at the maxTokens ceiling (${params.maxTokens ?? DEFAULT_MAX_TOKENS} tokens) and the cut-off Typst source does not compile — repair cannot help; raise maxTokens. Compile error: ${compiled.error}`,\n );\n }\n if (repairs >= MAX_COMPILE_REPAIRS) {\n throw new Error(\n `Typst compilation failed after ${MAX_COMPILE_REPAIRS} repair attempts: ${compiled.error}`,\n );\n }\n repairs++;\n logger.warn('Typst compile failed — feeding the error back for repair', {\n attempt: repairs,\n error: compiled.error.slice(0, 500),\n });\n generated = await generateResourceFromTopic(\n title, entityTypes, inferenceClient, logger,\n params.prompt, params.language, params.context, params.temperature,\n params.maxTokens, params.sourceLanguage, outputMediaType,\n params.task, params.structure, params.cite,\n { source, error: compiled.error },\n );\n if (validIds) {\n const resolved = resolveCitationTokens(generated.content, validIds, logger);\n source = resolved.content;\n citations = resolved.citations;\n } else {\n source = generated.content;\n }\n compiled = compileTypst(source);\n }\n\n assertWithinOutputBudget(compiled.pdf.byteLength);\n onProgress(95, { code: 'creating-resource' });\n // The producer owns terminality (GENERATE-FROM-RESOURCE P1/D1): without\n // this, the client's last frame is forever the 95% payload. Generic by\n // design — the outcome (name + link) travels on job:complete (D8).\n // `truncated` rides both surfaces (P3a/D6): truncation that lands at a\n // syntactic boundary still compiles, and the artifact is still cut off.\n onProgress(100, { code: 'complete-generated', truncated: generated.truncated });\n\n return {\n content: compiled.pdf,\n title,\n format: outputMediaType,\n citations,\n result: {\n kind: 'generation',\n resourceId: '' as ResourceId,\n resourceName: title,\n truncated: generated.truncated,\n },\n };\n }\n\n // Generation has exactly two observable transitions: the LLM call starting\n // ('generating') and content finalized / creation beginning ('creating').\n // There is no fetch — context arrives pre-gathered in params. Percentages\n // approximate the share of expected wall-clock complete at each transition\n // (a single atomic LLM call has no measurable progress, and inference\n // dominates the job): its start is ~5, its end ~95.\n onProgress(5, { code: 'generating-resource' });\n\n const generated = await generateResourceFromTopic(\n title,\n entityTypes,\n inferenceClient,\n logger,\n params.prompt,\n params.language,\n params.context,\n params.temperature,\n params.maxTokens,\n params.sourceLanguage,\n outputMediaType,\n params.task,\n params.structure,\n params.cite,\n );\n\n // Under `cite`, the model emitted [[<id>]] transport tokens — resolve them:\n // validate against the ids the context actually contained, strip from the\n // stored content, and carry claim-span citations for the worker to mint.\n // When cite is off, bracketed text is legitimate content — leave it alone.\n let content = generated.content;\n let citations: GenerationCitation[] = [];\n if (params.cite === true) {\n const resolved = resolveCitationTokens(content, collectContextResourceIds(params.context), logger);\n content = resolved.content;\n citations = resolved.citations;\n }\n\n onProgress(95, { code: 'creating-resource' });\n\n // The artifact is bytes; text is an encoding of them. One shape for every\n // output media type, so a string can never travel mislabeled as a binary\n // format (PDF-GENERATION P1). Citation offsets index the decoded text.\n const artifact = new TextEncoder().encode(content);\n assertWithinOutputBudget(artifact.byteLength);\n\n // The producer owns terminality (GENERATE-FROM-RESOURCE P1/D1): without\n // this, the client's last frame is forever the 95% payload. Generic by\n // design — the outcome (name + link) travels on job:complete (D8).\n // `truncated` rides both surfaces (P3a/D6): the event is the frame the\n // client renders, the result is the record.\n onProgress(100, { code: 'complete-generated', truncated: generated.truncated });\n\n return {\n content: artifact,\n title,\n format: outputMediaType,\n citations,\n result: {\n kind: 'generation',\n resourceId: '' as ResourceId,\n resourceName: title,\n truncated: generated.truncated,\n },\n };\n}\n","/**\n * Worker Runtime — the importable half of the worker host.\n *\n * `worker-main.ts` is a process entrypoint (config at module scope,\n * `main()` at import) and therefore untestable by construction; everything\n * a unit test needs to reach lives here instead, fully parameterized — no\n * module-scope env reads, no side effects at import.\n *\n * The load-bearing contract this module owns (and the reason it was\n * extracted): a worker's stamped identity is the DID the\n * `/api/tokens/agent` exchange MINTED for it, carried verbatim — never\n * re-derived from the URL the worker happens to dial. One logical agent\n * previously got two DIDs that way (.plans/bugs/agent-did-host-skew.md).\n */\n\nimport { startWorkerProcess } from './worker-process';\nimport type { WorkerVitals } from './job-claim-adapter';\nimport { anchoredTextOverBus } from './anchored-text-over-bus';\nimport type { InferenceClient } from '@semiont/inference';\nimport { hostname } from 'os';\nimport {\n replyChannelsFor,\n didToAgent,\n baseUrl,\n retryWithBackoff,\n isTransientFetchError,\n STARTUP_FETCH_RETRY,\n type BusOperationKey,\n type RetryPolicy,\n type components,\n type Logger,\n type AccessToken,\n} from '@semiont/core';\nimport {\n InMemorySessionStorage,\n SemiontClient,\n SemiontSession,\n kbGatewayUrl,\n setStoredSession,\n type HttpEndpoint,\n type KbTarget,\n} from '@semiont/sdk';\nimport { HttpContentTransport, HttpTransport } from '@semiont/http-transport';\nimport type { ContentReads } from '@semiont/content';\nimport { BehaviorSubject } from 'rxjs';\n\ntype Agent = components['schemas']['Agent'];\n\n/** Shape of each resolved worker inference entry under `_metadata.workers`. */\nexport type ResolvedInference = {\n type: 'anthropic' | 'ollama';\n model: string;\n apiKey?: string;\n endpoint?: string;\n baseURL?: string;\n};\n\n/** One agent identity: an inference engine and the job types it serves. */\nexport interface AgentGroup {\n inference: ResolvedInference;\n jobTypes: string[];\n client: InferenceClient;\n}\n\nexport interface WorkerRuntimeOptions {\n group: AgentGroup;\n /** The gateway URL this worker dials — connection topology ONLY, never identity. */\n gatewayBaseUrl: string;\n /** Shared secret for `/api/tokens/agent`, and the bearer the byte reads\n * below show the Archivist. */\n workerSecret: string;\n /**\n * The resource's bytes, for detection's extraction seam. Built by the\n * entrypoint (`worker-main`) rather than here, so a worker with no\n * Archivist configured refuses at boot instead of failing every job\n * (SINGLE-KB-MOUNT P4).\n */\n contentReads: ContentReads;\n logger: Logger;\n}\n\n/** Per-agent liveness: the adapter's snapshot plus this agent's identity. */\nexport interface AgentVitals extends WorkerVitals {\n provider: string;\n model: string;\n did: string;\n jobTypes: string[];\n}\n\nexport interface AgentWorkerHandle {\n session: SemiontSession;\n vitals(): AgentVitals;\n dispose(): Promise<void>;\n}\n\nexport interface WorkerHealthPayload {\n status: 'ok';\n agents: number;\n workers: AgentVitals[];\n}\n\n/**\n * The `/health` body (WORKER-LIVENESS.md P1). Additive: existing\n * consumers (image HEALTHCHECK, compose `service_healthy`, `semiont start`)\n * keep reading `status`/`agents`; the per-agent vitals expose\n * claim-loop progress so a stalled worker is *visible*, not just alive.\n */\nexport function buildHealthPayload(workers: ReadonlyArray<{ vitals(): AgentVitals }>): WorkerHealthPayload {\n return {\n status: 'ok',\n agents: workers.length,\n workers: workers.map((w) => w.vitals()),\n };\n}\n\n/**\n * Stall watchdog (WORKER-LIVENESS.md P3) — the fail-fast line behind the\n * inference timeout. There is no poll loop to heartbeat; the honest\n * stall signal in this push-driven architecture is *processing without\n * activity*: an agent holding a claimed job whose `lastActivityAt`\n * (claim / progress / finish) has stopped advancing is wedged — the\n * adapter ignores every announcement while `isProcessing`, so a wedged\n * agent never recovers on its own. Silent hang → loud crash → whatever\n * restart policy the deployment chose.\n *\n * Thresholds are fixed by design (no env knobs) and deliberately\n * layered: inference timeout (10 min, P2) fires first; this watchdog\n * (15 min) catches wedges where the loop still turns but activity has\n * stopped; the gateway's dead-worker janitor (30 min) re-queues the job\n * regardless.\n *\n * The layering matters because this watchdog has a hard limit: it is an\n * IN-PROCESS timer, so it cannot fire while the event loop itself is\n * blocked — the exact condition a blocked loop creates\n * (JOB-RESTART-SAFETY P7, the 2026-09-03 finalization hang: 18 min silent,\n * this watchdog never fired, an empty /health confirming the loop was\n * wedged). The unbounded emit that caused that specific hang is now bounded\n * at the transport (`EMIT_TIMEOUT_MS`), so the loop errors instead of\n * blocking; but for any future blocked-loop bug the ONLY backstop is the\n * out-of-process one — the gateway's janitor sweeping this worker's job\n * files by mtime (`fs-job-queue.ts` `recoverStaleRunningJobs`). A liveness\n * guarantee a blocked loop defeats is not one; the janitor is the guarantee.\n */\nexport const STALL_THRESHOLD_MS = 15 * 60_000;\nexport const STALL_CHECK_INTERVAL_MS = 60_000;\n\nexport interface StallWatchdogOptions {\n workers: ReadonlyArray<{ vitals(): AgentVitals }>;\n logger: Logger;\n /** Test seam; defaults to process.exit. */\n exit?: (code: number) => void;\n}\n\nexport function startStallWatchdog(opts: StallWatchdogOptions): { dispose(): void } {\n const { workers, logger, exit = (code: number) => process.exit(code) } = opts;\n\n const timer = setInterval(() => {\n const now = Date.now();\n for (const worker of workers) {\n const v = worker.vitals();\n if (!v.activeJob || !v.lastActivityAt) continue;\n\n const silentForMs = now - Date.parse(v.lastActivityAt);\n if (silentForMs <= STALL_THRESHOLD_MS) continue;\n\n logger.error('Worker stalled — exiting for restart', {\n provider: v.provider,\n model: v.model,\n did: v.did,\n jobId: v.activeJob.jobId,\n jobType: v.activeJob.type,\n processingSince: v.activeJob.since,\n lastActivityAt: v.lastActivityAt,\n silentForMs,\n thresholdMs: STALL_THRESHOLD_MS,\n });\n clearInterval(timer);\n exit(1);\n return;\n }\n }, STALL_CHECK_INTERVAL_MS);\n timer.unref?.();\n\n return { dispose: () => clearInterval(timer) };\n}\n\n/**\n * The bus operations a worker process ever AWAITS a reply to. Reply channels\n * are global fan-out on the gateway, so a full `BRIDGED_CHANNELS`\n * subscription made every worker receive every other client's reply traffic\n * — measured at ~85 multi-MB `browse:annotations-result` frames/min during\n * the 2026-09-03 worker OOM, all parsed and dropped by cid filtering. The\n * worker subscribes exactly its own operations' reply channels instead.\n *\n * This list restates a fact the code owns (which operations worker code\n * paths call `busRequest` on); its gate is `busRequest`'s `isSubscribed`\n * probe — an operation missing here fails IMMEDIATELY with\n * `bus.unsubscribed` naming the channel, never a silent 30 s timeout.\n * (`job:queued` is not here: it is a broadcast, added by the claim\n * adapter via `addChannels`.)\n */\nexport const WORKER_AWAITED_OPERATIONS = [\n 'job:claim',\n 'browse:resource-requested',\n 'browse:anchored-text-by-checksum-requested',\n // Durability acknowledgement for a unit's annotations (JOB-RESTART-SAFETY\n // P6). The worker AWAITS this one — a unit may not advance until its\n // annotations are in the event log — so its replies must be in the narrow\n // channel set or every commit fails fast with `bus.unsubscribed`.\n 'mark:commit',\n] as const satisfies readonly BusOperationKey[];\n\n/** The derived global SSE channel set for a worker's transport. */\nexport const WORKER_CHANNELS: readonly string[] = replyChannelsFor(WORKER_AWAITED_OPERATIONS);\n\nexport function parseGatewayUrl(url: string): { protocol: 'http' | 'https'; host: string; port: number } {\n const parsed = new URL(url);\n const protocol = (parsed.protocol.replace(':', '') === 'https' ? 'https' : 'http') as 'http' | 'https';\n const host = parsed.hostname;\n const port = parsed.port\n ? Number(parsed.port)\n : protocol === 'https' ? 443 : 80;\n return { protocol, host, port };\n}\n\n/**\n * Exchange the worker secret for this agent's JWT and its canonical DID.\n * The DID is minted by the gateway (from its `site.domain`) — the caller\n * carries it verbatim.\n *\n * Connection-level failures (`TypeError: fetch failed`) are retried with\n * exponential backoff: the gateway may be mid-restart or the container\n * network still warming up when this process starts, and orchestration\n * runs workers with `--rm` and no restart policy — exiting on the first\n * failed fetch is permanent death. HTTP-level rejections (401 on a bad\n * secret) are NOT retried; the gateway is up and said no.\n */\nexport async function authenticateAgent(opts: {\n gatewayBaseUrl: string;\n workerSecret: string;\n provider: string;\n model: string;\n logger?: Logger;\n retry?: RetryPolicy;\n}): Promise<{ token: string; did: string }> {\n const { gatewayBaseUrl, workerSecret, provider, model, logger, retry = STARTUP_FETCH_RETRY } = opts;\n if (!workerSecret) {\n throw new Error('SEMIONT_WORKER_SECRET is required to authenticate worker agents');\n }\n\n return retryWithBackoff(\n async () => {\n const response = await fetch(`${gatewayBaseUrl}/api/tokens/agent`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ secret: workerSecret, provider, model }),\n });\n\n if (!response.ok) {\n throw new Error(`Agent authentication failed for ${provider}:${model}: ${response.status} ${response.statusText}`);\n }\n\n return await response.json() as { token: string; did: string };\n },\n isTransientFetchError,\n retry,\n ({ attempt, attempts, delayMs, error }) => {\n logger?.warn('Gateway unreachable, retrying agent authentication', {\n agent: `${provider}:${model}`,\n attempt,\n attempts,\n retryInMs: delayMs,\n error: error instanceof Error ? error.message : String(error),\n });\n },\n );\n}\n\nexport async function startAgentWorker(\n opts: WorkerRuntimeOptions,\n): Promise<AgentWorkerHandle> {\n const { group, gatewayBaseUrl, workerSecret, contentReads, logger } = opts;\n const { inference } = group;\n\n const { protocol, host, port } = parseGatewayUrl(gatewayBaseUrl);\n const { token: initialToken, did } = await authenticateAgent({\n gatewayBaseUrl,\n workerSecret,\n provider: inference.type,\n model: inference.model,\n logger,\n });\n\n // The exchange minted this worker's canonical DID (from the gateway's\n // site.domain) and we carry it VERBATIM — never re-derive identity from\n // the URL we happen to dial (`host` is connection topology only). One\n // logical agent previously got two DIDs this way:\n // .plans/bugs/agent-did-host-skew.md.\n const generator: Agent = didToAgent(did);\n\n const kbId = `agent-${inference.type}-${inference.model}-${hostname()}`;\n const endpoint: HttpEndpoint = { kind: 'http', host, port, protocol };\n const kb: KbTarget = {\n id: kbId,\n label: `${inference.type} / ${inference.model} @ ${host}`,\n email: `agent@${host}`,\n endpoint,\n };\n const storage = new InMemorySessionStorage();\n setStoredSession(storage, kbId, { access: initialToken, refresh: '' });\n\n const token$ = new BehaviorSubject<AccessToken | null>(null);\n let session!: SemiontSession;\n const transport = new HttpTransport({\n baseUrl: baseUrl(kbGatewayUrl(endpoint)),\n token$,\n tokenRefresher: () => session.refresh().then((t) => t ?? null),\n // Only the reply channels this process awaits — not the full bridged\n // set. See WORKER_AWAITED_OPERATIONS.\n channels: WORKER_CHANNELS,\n });\n const content = new HttpContentTransport(transport);\n const client = new SemiontClient(transport, content, transport);\n session = new SemiontSession({\n kb,\n storage,\n client,\n token$,\n refresh: async () => {\n try {\n const { token } = await authenticateAgent({\n gatewayBaseUrl,\n workerSecret,\n provider: inference.type,\n model: inference.model,\n logger,\n });\n return token;\n } catch (err) {\n logger.error('Agent token refresh failed', {\n error: err instanceof Error ? err.message : String(err),\n agent: did,\n });\n return null;\n }\n },\n onError: (err) => {\n logger.error('Session error', { code: err.code, message: err.message, agent: did });\n },\n });\n await session.ready;\n\n const adapter = startWorkerProcess({\n session,\n jobTypes: group.jobTypes,\n inferenceClient: group.client,\n generator,\n // The extraction seam's cache, consulted over the bus and never written\n // (ANCHORED-TEXT-TO-SMELTER D2): the Smelter owns this store, the\n // Archivist answers the checksum-addressed read, and a worker that\n // misses extracts locally and discards.\n anchoredTextStore: anchoredTextOverBus(client, logger),\n contentReads,\n logger,\n });\n\n logger.info('Agent ready', {\n did,\n provider: inference.type,\n model: inference.model,\n jobTypes: group.jobTypes,\n });\n\n return {\n session,\n vitals: () => ({\n provider: inference.type,\n model: inference.model,\n did,\n jobTypes: group.jobTypes,\n ...adapter.vitals(),\n }),\n dispose: async () => {\n adapter.dispose();\n await session.dispose();\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/will-retry.ts","../src/fs-job-queue.ts","../src/types.ts","../src/workers/inference-call.ts","../src/failure-class.ts","../src/workers/detection/detection-chunking.ts","../src/workers/detection/motivation-prompts.ts","../src/workers/detection/anchor-audit.ts","../src/workers/detection/motivation-parsers.ts","../src/workers/annotation-detection.ts","../src/workers/detection/entity-extractor.ts","../src/workers/generation/resource-generation.ts","../src/workers/generation/typst-compiler.ts","../src/workers/generation/citation-resolver.ts","../src/workers/detection/bounded-concurrency.ts","../src/processors.ts","../src/worker-runtime.ts"],"names":["fs","toJobId","StructuredReadError","isObject","isString","chunkText","getLocaleEnglishName","estimateTokens","response","join","reconcileSelector","generated","citations"],"mappings":"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","file":"index.js","sourcesContent":["import type { JobMetadata } from './types';\n\n/**\n * Will this failure be re-queued for another attempt?\n *\n * ONE decision site, deliberately (JOB-RESTART-SAFETY P5). Two places need\n * the answer and they must never disagree:\n *\n * - `FsJobQueue.failJob` acts on it — re-queue or write the terminal record;\n * - the worker reports it on `job:fail` as `willRetry`, so a client's\n * job-watch stream knows whether the failure it just saw is the end.\n *\n * A second copy of this predicate would be the classic N-places-decide-one-\n * thing bug: the client would end a stream the queue then revived, or hold a\n * stream open on a job nobody will run again. The worker reads the budget off\n * the record it claimed, and `retryCount` only changes inside `failJob`, so\n * the two evaluations see the same numbers.\n *\n * A known-deterministic failure skips the budget outright — the same request\n * cannot succeed on a second attempt, so a retry is guaranteed waste at full\n * price. Unclassified failures are treated as transient.\n */\nexport function willRetryAfter(\n metadata: Pick<JobMetadata, 'retryCount' | 'maxRetries'>,\n failureClass?: 'transient' | 'deterministic',\n): boolean {\n return failureClass !== 'deterministic' && metadata.retryCount < metadata.maxRetries;\n}\n","/**\n * Job Queue Manager\n *\n * Filesystem-based job queue with atomic operations.\n * Jobs are stored in directories by status; status transitions are\n * atomic delete + write across directories.\n */\n\nimport { promises as fs } from 'fs';\nimport * as path from 'path';\nimport type { AnyJob, JobStatus, JobQueryFilters, CancelledJob, CompleteJob, FailedJob, PendingJob, RunningJob } from './types';\nimport type { SemiontState } from '@semiont/core/node';\nimport { jobId as toJobId, type JobId, type Logger, type EventBus } from '@semiont/core';\nimport type { JobQueue } from './job-queue-interface';\nimport { willRetryAfter } from './will-retry';\n\n/**\n * How often pending jobs are re-announced on `job:queued` and stale\n * running jobs are checked for recovery.\n *\n * The announcement in `createJob` only reaches workers that are\n * connected and idle at that moment. Re-announcing every pending job\n * on an interval restores catch-up for everything that announcement\n * misses: all eligible workers busy, a worker offline or mid-SSE-\n * reconnect, or a gateway restart with a pending backlog. Claim\n * arbitration (the `job:claim` handler refuses non-pending jobs)\n * makes duplicate announcements harmless.\n */\nconst REANNOUNCE_INTERVAL_MS = 30_000;\n\n/**\n * A running job whose file hasn't been touched for this long is\n * presumed orphaned by a dead worker and fed through the retry-or-fail\n * path. Progress writes (`recordProgress`) refresh the file's mtime,\n * so this is a heartbeat timeout, not a job-duration limit — but a\n * worker that emits no progress for the whole window will be falsely\n * recovered, so it stays deliberately generous.\n */\nconst STALE_RUNNING_MS = 30 * 60_000;\n\n/** Minimum spacing between progress writes per job — workers can be chatty. */\nconst PROGRESS_WRITE_MIN_INTERVAL_MS = 5_000;\n\n/** Terminal jobs (complete/failed/cancelled) are pruned after this long. */\nconst RETENTION_HOURS = 24;\n\n/** How often the retention pruning runs. */\nconst CLEANUP_INTERVAL_MS = 3_600_000;\n\nexport class FsJobQueue implements JobQueue {\n private jobsDir: string;\n private logger: Logger;\n private reannounceTimer: ReturnType<typeof setInterval> | null = null;\n private cleanupTimer: ReturnType<typeof setInterval> | null = null;\n /** Per-job timestamp of the last progress write, for throttling. */\n private lastProgressWrite = new Map<string, number>();\n\n constructor(\n /** `SemiontState` and not `SemiontProject`: the queue reads ONE path,\n * and the gateway that owns it mounts no KB tree (SINGLE-KB-MOUNT P5). */\n state: SemiontState,\n logger: Logger,\n private eventBus?: EventBus\n ) {\n this.jobsDir = state.jobsDir;\n this.logger = logger;\n }\n\n /**\n * Initialize job queue directories, announce any pending backlog,\n * and start the re-announce interval. Idempotent.\n */\n async initialize(): Promise<void> {\n const statuses: JobStatus[] = ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const dir = path.join(this.jobsDir, status);\n await fs.mkdir(dir, { recursive: true });\n }\n\n if (this.eventBus && !this.reannounceTimer) {\n // Jobs left pending across a restart are announced right away…\n await this.announcePendingJobs();\n // …and anything that misses an announcement is retried here, along\n // with recovery of jobs orphaned by a dead worker.\n this.reannounceTimer = setInterval(() => {\n this.announcePendingJobs().catch((error) => {\n this.logger.warn('Pending-job re-announce failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n this.recoverStaleRunningJobs().catch((error) => {\n this.logger.warn('Stale-running recovery failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }, REANNOUNCE_INTERVAL_MS);\n // Don't let the interval keep the process alive on shutdown.\n this.reannounceTimer.unref?.();\n }\n\n if (!this.cleanupTimer) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupOldJobs(RETENTION_HOURS).catch((error) => {\n this.logger.warn('Job retention cleanup failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }, CLEANUP_INTERVAL_MS);\n this.cleanupTimer.unref?.();\n }\n\n this.logger.info('Job queue initialized');\n }\n\n /**\n * Stop the re-announce and retention intervals\n */\n destroy(): void {\n if (this.reannounceTimer) {\n clearInterval(this.reannounceTimer);\n this.reannounceTimer = null;\n }\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = null;\n }\n }\n\n /**\n * Emit `job:queued` for a pending job, if an EventBus is wired and\n * the job carries a `resourceId` (every current job type does).\n */\n private announce(job: AnyJob): void {\n if (this.eventBus && 'params' in job && 'resourceId' in job.params) {\n this.eventBus.get('job:queued').next({\n jobId: job.metadata.id,\n jobType: job.metadata.type,\n resourceId: job.params.resourceId,\n userId: job.metadata.userId,\n });\n }\n }\n\n /**\n * Announce every job currently in `pending/`. Files that vanish or\n * fail to parse mid-scan (claimed, cancelled, partially written)\n * are skipped — they're either gone for a good reason or picked up\n * on the next tick.\n */\n private async announcePendingJobs(): Promise<void> {\n const pendingDir = path.join(this.jobsDir, 'pending');\n let files: string[];\n try {\n files = await fs.readdir(pendingDir);\n } catch {\n return;\n }\n files.sort();\n for (const file of files) {\n try {\n const content = await fs.readFile(path.join(pendingDir, file), 'utf-8');\n this.announce(JSON.parse(content) as AnyJob);\n } catch {\n // Skip unreadable files\n }\n }\n }\n\n /**\n * Create a new job\n */\n async createJob(job: AnyJob): Promise<void> {\n const jobPath = this.getJobPath(job.metadata.id, job.status);\n await fs.writeFile(jobPath, JSON.stringify(job, null, 2), 'utf-8');\n this.logger.info('Job created', { jobId: job.metadata.id, status: job.status });\n\n if (job.status === 'pending') {\n this.announce(job);\n }\n }\n\n /**\n * Get a job by ID (searches all status directories)\n */\n async getJob(jobId: JobId): Promise<AnyJob | null> {\n const statuses: JobStatus[] = ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const jobPath = this.getJobPath(jobId, status);\n try {\n const content = await fs.readFile(jobPath, 'utf-8');\n return JSON.parse(content) as AnyJob;\n } catch (error) {\n // File doesn't exist in this status directory, try next\n continue;\n }\n }\n\n return null;\n }\n\n /**\n * Update a job (atomic: delete old, write new)\n */\n async updateJob(job: AnyJob, oldStatus?: JobStatus): Promise<void> {\n // If oldStatus provided, delete from old location\n if (oldStatus && oldStatus !== job.status) {\n const oldPath = this.getJobPath(job.metadata.id, oldStatus);\n try {\n await fs.unlink(oldPath);\n } catch (error) {\n // Ignore if file doesn't exist\n }\n }\n\n // Write to new location\n const newPath = this.getJobPath(job.metadata.id, job.status);\n await fs.writeFile(newPath, JSON.stringify(job, null, 2), 'utf-8');\n\n if (oldStatus && oldStatus !== job.status) {\n this.logger.info('Job moved', { jobId: job.metadata.id, oldStatus, newStatus: job.status });\n // Re-entering pending (e.g. a retry) is a fresh announcement.\n if (job.status === 'pending') {\n this.announce(job);\n }\n } else {\n this.logger.info('Job updated', { jobId: job.metadata.id, status: job.status });\n }\n }\n\n /**\n * Move a running job to `complete`. Returns false (and changes\n * nothing) if the job is missing or not running — which also makes\n * duplicate `job:complete` events harmless.\n */\n async completeJob(jobId: JobId, result: Record<string, unknown>): Promise<boolean> {\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n return false;\n }\n\n this.lastProgressWrite.delete(jobId);\n const completed: CompleteJob<any, any> = {\n status: 'complete',\n metadata: job.metadata,\n params: job.params,\n startedAt: job.startedAt,\n completedAt: new Date().toISOString(),\n result,\n };\n await this.updateJob(completed, 'running');\n return true;\n }\n\n /**\n * Retry-or-fail a running job. While `retryCount < maxRetries` the\n * job goes back to `pending` with the count bumped (and is\n * re-announced); after that it lands in `failed` with the error.\n * Returns null (and changes nothing) if the job isn't running.\n */\n async failJob(jobId: JobId, error: string, completedUnits?: string[], failureClass?: 'transient' | 'deterministic'): Promise<'retried' | 'failed' | null> {\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n return null;\n }\n\n this.lastProgressWrite.delete(jobId);\n\n // Checkpointed resume (ABANDONED-INFERENCE P2): union this attempt's\n // completed units with earlier attempts' — attempt 3 must keep what\n // attempt 1 finished. Stored in metadata so the spread below (and in\n // every later rebuild) carries it forward; the terminal failed record\n // keeps it too, so the waste of a discarded run stays visible (D3).\n const checkpoint = [...new Set([...(job.metadata.completedUnits ?? []), ...(completedUnits ?? [])])];\n const metadata = checkpoint.length > 0\n ? { ...job.metadata, completedUnits: checkpoint }\n : job.metadata;\n\n // A known-deterministic failure skips the budget outright: the same\n // request cannot succeed on a second attempt, so a retry is guaranteed\n // waste at full price (ABANDONED-INFERENCE P3, A4). Unclassified and\n // transient failures retry as before.\n if (willRetryAfter(job.metadata, failureClass)) {\n const retried: PendingJob<any> = {\n status: 'pending',\n metadata: { ...metadata, retryCount: job.metadata.retryCount + 1 },\n params: job.params,\n };\n await this.updateJob(retried, 'running');\n return 'retried';\n }\n\n if (failureClass === 'deterministic' && job.metadata.retryCount < job.metadata.maxRetries) {\n this.logger.info('Job failed without retry — deterministic failure', { jobId, error });\n }\n\n const failed: FailedJob<any> = {\n status: 'failed',\n metadata,\n params: job.params,\n startedAt: job.startedAt,\n completedAt: new Date().toISOString(),\n error,\n };\n await this.updateJob(failed, 'running');\n return 'failed';\n }\n\n /**\n * Persist a running job's completed-unit checkpoint at unit completion\n * (JOB-RESTART-SAFETY P2). `failJob` writes this checkpoint on a clean\n * failure, but a worker that dies without emitting `job:fail` would lose\n * its finished units — so the janitor's recovery (which re-queues with\n * whatever `metadata.completedUnits` the running file holds) would redo\n * them. Writing it HERE, as each unit lands, makes a crash lose at most\n * the in-flight unit. Unioned with any existing checkpoint, unthrottled\n * (a unit completion is rare and must never be dropped), a no-op for\n * non-running jobs. Written directly, like `recordProgress`, so it also\n * refreshes the mtime heartbeat.\n */\n async checkpointUnits(jobId: JobId, completedUnits: string[]): Promise<void> {\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n return;\n }\n const merged = [...new Set([...(job.metadata.completedUnits ?? []), ...completedUnits])];\n const updated: RunningJob<any, any> = {\n ...job,\n metadata: { ...job.metadata, completedUnits: merged },\n };\n await fs.writeFile(this.getJobPath(jobId, 'running'), JSON.stringify(updated, null, 2), 'utf-8');\n }\n\n /**\n * Write progress into a running job's file. Throttled per job, and\n * a no-op for jobs that aren't running. Beyond surfacing live\n * progress to `job:status-requested`, each write refreshes the\n * file's mtime — the heartbeat `recoverStaleRunningJobs` watches.\n */\n async recordProgress(jobId: JobId, progress: Record<string, unknown>): Promise<void> {\n const now = Date.now();\n const lastWrite = this.lastProgressWrite.get(jobId) ?? 0;\n if (now - lastWrite < PROGRESS_WRITE_MIN_INTERVAL_MS) {\n return;\n }\n this.lastProgressWrite.set(jobId, now);\n\n const job = await this.getJob(jobId);\n if (!job || job.status !== 'running') {\n this.lastProgressWrite.delete(jobId);\n return;\n }\n\n // Written directly (not via updateJob) so chatty progress doesn't\n // flood the info log.\n const updated: RunningJob<any, any> = { ...job, progress };\n await fs.writeFile(this.getJobPath(jobId, 'running'), JSON.stringify(updated, null, 2), 'utf-8');\n }\n\n /**\n * List jobs with filters\n */\n async listJobs(filters: JobQueryFilters = {}): Promise<AnyJob[]> {\n const jobs: AnyJob[] = [];\n\n // Determine which status directories to scan\n const statuses: JobStatus[] = filters.status\n ? [filters.status]\n : ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const statusDir = path.join(this.jobsDir, status);\n\n try {\n const files = await fs.readdir(statusDir);\n\n for (const file of files) {\n const jobPath = path.join(statusDir, file);\n const content = await fs.readFile(jobPath, 'utf-8');\n const job = JSON.parse(content) as AnyJob;\n\n // Apply filters\n if (filters.type && job.metadata.type !== filters.type) continue;\n if (filters.userId && job.metadata.userId !== filters.userId) continue;\n\n jobs.push(job);\n }\n } catch (error) {\n // Directory might not exist yet\n continue;\n }\n }\n\n // Sort by created descending (newest first)\n jobs.sort((a, b) => new Date(b.metadata.created).getTime() - new Date(a.metadata.created).getTime());\n\n // Apply pagination\n const offset = filters.offset || 0;\n const limit = filters.limit || 100;\n\n return jobs.slice(offset, offset + limit);\n }\n\n /**\n * Cancel a job\n */\n async cancelJob(jobId: JobId): Promise<boolean> {\n const job = await this.getJob(jobId);\n\n if (!job) {\n return false;\n }\n\n // Can only cancel pending or running jobs\n if (job.status !== 'pending' && job.status !== 'running') {\n return false;\n }\n\n const oldStatus = job.status;\n\n // Create cancelled job with proper structure\n const cancelledJob: CancelledJob<any> = {\n status: 'cancelled',\n metadata: job.metadata,\n params: job.status === 'pending' ? job.params : job.params,\n startedAt: job.status === 'running' ? job.startedAt : undefined,\n completedAt: new Date().toISOString(),\n };\n\n await this.updateJob(cancelledJob, oldStatus);\n return true;\n }\n\n /**\n * Cancel all pending jobs in a category — the granularity of the\n * `job:cancel-requested` UI signal. Running jobs are left to finish:\n * interrupting a worker mid-inference would need a worker-side kill\n * channel that doesn't exist.\n */\n async cancelPendingJobs(category: 'annotation' | 'generation'): Promise<number> {\n const matches = category === 'generation'\n ? (type: string) => type === 'generation'\n : (type: string) => type.endsWith('-annotation');\n\n const pending = await this.listJobs({ status: 'pending', limit: Number.MAX_SAFE_INTEGER });\n let cancelled = 0;\n for (const job of pending) {\n if (!matches(job.metadata.type)) continue;\n if (await this.cancelJob(job.metadata.id)) {\n cancelled++;\n }\n }\n\n if (cancelled > 0) {\n this.logger.info('Cancelled pending jobs', { category, cancelled });\n }\n return cancelled;\n }\n\n /**\n * Recover running jobs orphaned by a dead worker: any `running/`\n * file whose mtime is older than the stale window is fed through\n * the same retry-or-fail path as `job:fail`. Progress writes\n * refresh the mtime, so a live worker is never recovered out from\n * under itself as long as it reports within the window.\n */\n async recoverStaleRunningJobs(): Promise<number> {\n const runningDir = path.join(this.jobsDir, 'running');\n let files: string[];\n try {\n files = await fs.readdir(runningDir);\n } catch {\n return 0;\n }\n\n const now = Date.now();\n let recovered = 0;\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n try {\n const stat = await fs.stat(path.join(runningDir, file));\n if (now - stat.mtimeMs < STALE_RUNNING_MS) continue;\n\n const staleId = toJobId(file.slice(0, -'.json'.length));\n const outcome = await this.failJob(\n staleId,\n `worker presumed dead — no progress within ${STALE_RUNNING_MS / 60_000} minutes`,\n );\n if (outcome) {\n this.logger.warn('Recovered stale running job', { jobId: staleId, outcome });\n recovered++;\n }\n } catch {\n // File vanished mid-scan (job finished) — nothing to recover\n }\n }\n return recovered;\n }\n\n /**\n * Clean up old completed/failed jobs (older than retention period)\n */\n async cleanupOldJobs(retentionHours: number = 24): Promise<number> {\n const cutoffTime = Date.now() - (retentionHours * 60 * 60 * 1000);\n let deletedCount = 0;\n\n const cleanupStatuses: JobStatus[] = ['complete', 'failed', 'cancelled'];\n\n for (const status of cleanupStatuses) {\n const statusDir = path.join(this.jobsDir, status);\n\n try {\n const files = await fs.readdir(statusDir);\n\n for (const file of files) {\n const jobPath = path.join(statusDir, file);\n const content = await fs.readFile(jobPath, 'utf-8');\n const job = JSON.parse(content) as AnyJob;\n\n if (job.status === 'complete' || job.status === 'failed' || job.status === 'cancelled') {\n const completedTime = new Date(job.completedAt).getTime();\n\n if (completedTime < cutoffTime) {\n await fs.unlink(jobPath);\n deletedCount++;\n }\n }\n }\n } catch (error) {\n this.logger.error('Error cleaning up jobs', { status, error: error instanceof Error ? error.message : String(error) });\n }\n }\n\n if (deletedCount > 0) {\n this.logger.info('Jobs cleaned up', { deletedCount });\n }\n\n return deletedCount;\n }\n\n /**\n * Get job file path\n */\n private getJobPath(jobId: JobId, status: JobStatus): string {\n return path.join(this.jobsDir, status, `${jobId}.json`);\n }\n\n /**\n * Get statistics about the queue\n */\n async getStats(): Promise<{\n pending: number;\n running: number;\n complete: number;\n failed: number;\n cancelled: number;\n }> {\n const stats = {\n pending: 0,\n running: 0,\n complete: 0,\n failed: 0,\n cancelled: 0\n };\n\n const statuses: JobStatus[] = ['pending', 'running', 'complete', 'failed', 'cancelled'];\n\n for (const status of statuses) {\n const statusDir = path.join(this.jobsDir, status);\n\n try {\n const files = await fs.readdir(statusDir);\n stats[status] = files.length;\n } catch (error) {\n // Directory might not exist yet\n stats[status] = 0;\n }\n }\n\n return stats;\n }\n}\n","/**\n * Job Queue Type Definitions - Discriminated Union Design\n *\n * Jobs represent async work that can be queued, processed, and monitored.\n * Uses TypeScript discriminated unions to enforce valid state transitions.\n *\n * Design principles:\n * - Each job status has specific valid fields\n * - Type narrowing works automatically via status discriminant\n * - No optional fields that may or may not exist\n * - State machine is explicit and type-safe\n */\n\nimport type { JobId, EntityType, ResourceId, UserId, GenerationJobParams, TagSchema } from '@semiont/core';\n\nexport type JobType = 'reference-annotation' | 'generation' | 'highlight-annotation' | 'assessment-annotation' | 'comment-annotation' | 'tag-annotation';\nexport type JobStatus = 'pending' | 'running' | 'complete' | 'failed' | 'cancelled';\n\n// ============================================================================\n// Core Metadata and Parameters\n// ============================================================================\n\n/**\n * Job metadata - common to all states\n */\nexport interface JobMetadata {\n id: JobId;\n type: JobType;\n userId: UserId;\n /**\n * Audit-only snapshot of the requesting user (with `userEmail` and\n * `userDomain` below), stamped at job creation and persisted in the\n * on-disk job file. No code path reads these back — annotation\n * `creator` attribution is derived from `userId` via `didToAgent()`.\n * Kept intentionally so job files are self-describing to a human.\n */\n userName: string;\n userEmail: string;\n userDomain: string;\n created: string;\n retryCount: number;\n maxRetries: number;\n /**\n * Checkpointed resume (ABANDONED-INFERENCE P2, HD1): the entity-type\n * units whose annotations were fully emitted by earlier failed\n * attempts. Written only by `failJob`, unioned across attempts — and\n * because `failJob` rebuilds the retried record by spreading metadata,\n * the checkpoint survives every subsequent rebuild for free. A retried\n * claim skips these units, so completed work is neither redone nor\n * duplicated.\n */\n completedUnits?: string[];\n}\n\n/**\n * Locale conventions for detection/generation params.\n *\n * Two independent locales flow through these jobs:\n *\n * - `language` — *annotation body* locale. The BCP-47 tag the LLM should\n * write generated body text in (comment text, assessment text, generated\n * resource content, tag category label). Sourced from the user's UI\n * locale. Stamped onto the W3C `TextualBody.language` field.\n *\n * - `sourceLanguage` — *source resource* locale. The BCP-47 tag of the\n * content being analyzed. Sourced from `ResourceDescriptor` (carried as\n * `Representation.language` on the primary representation). Used in\n * prompts so the LLM analyzes non-English source correctly even when\n * the user's UI locale differs.\n *\n * Examples: a German user analyzing an English document → `language='de'`,\n * `sourceLanguage='en'`. An English user detecting entities in a French\n * document → `language='en'` (unused for entity references), `sourceLanguage='fr'`.\n */\n\n/**\n * Detection job parameters\n */\nexport interface DetectionParams {\n resourceId: ResourceId;\n entityTypes: EntityType[];\n includeDescriptiveReferences?: boolean;\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n\n/**\n * Highlight detection job parameters\n */\nexport interface HighlightDetectionParams {\n resourceId: ResourceId;\n instructions?: string;\n density?: number;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n/**\n * Assessment detection job parameters\n */\nexport interface AssessmentDetectionParams {\n resourceId: ResourceId;\n instructions?: string;\n tone?: 'analytical' | 'critical' | 'balanced' | 'constructive';\n density?: number;\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n/**\n * Comment detection job parameters\n */\nexport interface CommentDetectionParams {\n resourceId: ResourceId;\n instructions?: string;\n tone?: 'scholarly' | 'explanatory' | 'conversational' | 'technical';\n density?: number;\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n/**\n * Tag detection job parameters.\n *\n * Carries the *full* `TagSchema` (not just an id). The dispatcher resolves\n * the caller-supplied `schemaId` against the per-KB tag-schema projection\n * at job-creation time and embeds the resolved schema here, keeping the\n * worker independent of the registry.\n */\nexport interface TagDetectionParams {\n resourceId: ResourceId;\n schema: TagSchema;\n categories: string[];\n /** Annotation body locale — see locale conventions above. */\n language?: string;\n /** Source-resource locale — see locale conventions above. */\n sourceLanguage?: string;\n}\n\n// ============================================================================\n// Progress and Result Types\n// ============================================================================\n\n/**\n * Detection job progress\n */\nexport interface DetectionProgress {\n totalEntityTypes: number;\n processedEntityTypes: number;\n entitiesFound: number;\n entitiesEmitted: number;\n}\n\n/**\n * Detection job result\n */\nexport interface DetectionResult {\n kind: 'reference-annotation';\n totalFound: number;\n totalEmitted: number;\n errors: number;\n}\n\n/**\n * Generation job progress\n */\nexport interface YieldProgress {\n /** The two real generation transitions — LLM call running, then persisting. */\n stage: 'generating' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Generation job result\n */\nexport interface GenerationResult {\n kind: 'generation';\n resourceId: ResourceId;\n resourceName: string;\n /** True when the model stopped at the maxTokens ceiling — the artifact is cut off, not complete (GENERATE-FROM-RESOURCE D6). */\n truncated: boolean;\n}\n\n/**\n * Highlight detection job progress\n */\nexport interface HighlightDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Highlight detection job result\n */\nexport interface HighlightDetectionResult {\n kind: 'highlight-annotation';\n highlightsFound: number;\n highlightsCreated: number;\n}\n\n/**\n * Assessment detection job progress\n */\nexport interface AssessmentDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Assessment detection job result\n */\nexport interface AssessmentDetectionResult {\n kind: 'assessment-annotation';\n assessmentsFound: number;\n assessmentsCreated: number;\n}\n\n/**\n * Comment detection job progress\n */\nexport interface CommentDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n message?: string;\n}\n\n/**\n * Comment detection job result\n */\nexport interface CommentDetectionResult {\n kind: 'comment-annotation';\n commentsFound: number;\n commentsCreated: number;\n}\n\n/**\n * Tag detection job progress\n */\nexport interface TagDetectionProgress {\n stage: 'analyzing' | 'creating';\n percentage: number;\n currentCategory?: string;\n processedCategories: number;\n totalCategories: number;\n message?: string;\n}\n\n/**\n * Tag detection job result\n */\nexport interface TagDetectionResult {\n kind: 'tag-annotation';\n tagsFound: number;\n tagsCreated: number;\n byCategory: Record<string, number>;\n}\n\n// ============================================================================\n// Generic Job State Types\n// ============================================================================\n\n/**\n * Pending job - just created, waiting to be picked up\n */\nexport interface PendingJob<P> {\n status: 'pending';\n metadata: JobMetadata;\n params: P;\n}\n\n/**\n * Running job - actively being processed\n */\nexport interface RunningJob<P, PG> {\n status: 'running';\n metadata: JobMetadata;\n params: P;\n startedAt: string;\n progress: PG;\n}\n\n/**\n * Complete job - successfully finished\n */\nexport interface CompleteJob<P, R> {\n status: 'complete';\n metadata: JobMetadata;\n params: P;\n startedAt: string;\n completedAt: string;\n result: R;\n}\n\n/**\n * Failed job - encountered an error\n */\nexport interface FailedJob<P> {\n status: 'failed';\n metadata: JobMetadata;\n params: P;\n startedAt?: string;\n completedAt: string;\n error: string;\n}\n\n/**\n * Cancelled job - stopped by user\n */\nexport interface CancelledJob<P> {\n status: 'cancelled';\n metadata: JobMetadata;\n params: P;\n startedAt?: string;\n completedAt: string;\n}\n\n/**\n * Generic job - discriminated union of all states\n */\nexport type Job<P, PG, R> =\n | PendingJob<P>\n | RunningJob<P, PG>\n | CompleteJob<P, R>\n | FailedJob<P>\n | CancelledJob<P>;\n\n// ============================================================================\n// Concrete Job Types\n// ============================================================================\n\nexport type DetectionJob = Job<DetectionParams, DetectionProgress, DetectionResult>;\nexport type GenerationJob = Job<GenerationJobParams, YieldProgress, GenerationResult>;\nexport type HighlightDetectionJob = Job<HighlightDetectionParams, HighlightDetectionProgress, HighlightDetectionResult>;\nexport type AssessmentDetectionJob = Job<AssessmentDetectionParams, AssessmentDetectionProgress, AssessmentDetectionResult>;\nexport type CommentDetectionJob = Job<CommentDetectionParams, CommentDetectionProgress, CommentDetectionResult>;\nexport type TagDetectionJob = Job<TagDetectionParams, TagDetectionProgress, TagDetectionResult>;\n\n/**\n * Discriminated union of all job types\n */\nexport type AnyJob = DetectionJob | GenerationJob | HighlightDetectionJob | AssessmentDetectionJob | CommentDetectionJob | TagDetectionJob;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\nconst JOB_TYPES: ReadonlySet<string> = new Set<JobType>([\n 'reference-annotation',\n 'generation',\n 'highlight-annotation',\n 'assessment-annotation',\n 'comment-annotation',\n 'tag-annotation',\n]);\n\n/**\n * Narrow a job type arriving off the bus, where it is only `string`.\n *\n * Worth checking rather than asserting: `jobType` is a required, enumerated\n * field on every job lifecycle command, so an unrecognized value produces a\n * payload the gateway will reject — better caught at the worker with a clear\n * error than as an opaque emit failure.\n */\nexport function isJobType(value: string): value is JobType {\n return JOB_TYPES.has(value);\n}\n\n/**\n * Narrow bus-delivered job params to the shape a processor expects.\n *\n * Job params cross the bus as `Record<string, unknown>`. Every params type in\n * this file requires exactly one field — `resourceId` — with the rest\n * optional, so that is what gets verified; absent optionals are legitimately\n * absent, not a validation failure. Callers pass the params type for the job\n * they have already branched on.\n */\nexport function asJobParams<T extends { resourceId: ResourceId }>(\n params: Record<string, unknown>,\n): T {\n if (typeof params.resourceId !== 'string') {\n throw new Error('Job params are missing a resourceId');\n }\n return params as unknown as T;\n}\n\n// Generation params are deliberately NOT covered: generation is the one job\n// that does not read a source resource (it mints one), so it requires no\n// `resourceId` and there is nothing to verify.\n\nexport function isPendingJob(job: AnyJob): job is PendingJob<any> {\n return job.status === 'pending';\n}\n\nexport function isRunningJob(job: AnyJob): job is RunningJob<any, any> {\n return job.status === 'running';\n}\n\nexport function isCompleteJob(job: AnyJob): job is CompleteJob<any, any> {\n return job.status === 'complete';\n}\n\nexport function isFailedJob(job: AnyJob): job is FailedJob<any> {\n return job.status === 'failed';\n}\n\nexport function isCancelledJob(job: AnyJob): job is CancelledJob<any> {\n return job.status === 'cancelled';\n}\n\n// ============================================================================\n// Job Query Types\n// ============================================================================\n\nexport interface JobQueryFilters {\n status?: JobStatus;\n type?: JobType;\n userId?: UserId;\n limit?: number;\n offset?: number;\n}\n\n","/**\n * Bounded inference calls — WORKER-LIVENESS.md P2 (G1: prevention).\n *\n * The claim loop's only unbounded await is the model call: bus\n * operations gained transport timeouts in 0.5.6, the inference HTTP\n * request did not. One request that never settles used to wedge the\n * worker forever — the adapter ignores announcements while\n * `isProcessing`, so a single stuck call silenced the whole agent.\n * Bounding the call converts that silent hang into an ordinary job\n * failure that flows through the existing `job:fail` path (and the\n * gateway's retry budget — a timeout is transient-shaped, so retrying\n * is correct) and frees the claim loop.\n *\n * This is a timeout AND a cancellation (ABANDONED-INFERENCE P1): on expiry\n * the bound aborts the underlying request through the `InferenceClient`\n * signal, so the transport — and, on the Anthropic path, the SDK's internal\n * retry loop — is torn down rather than left running as a billed zombie\n * (P0 caught one completing 24–34 minutes after its job was gone). The\n * timeout stays as the last line either way, exactly as before; the abort is\n * the addition, not the replacement (D1). The eventual settlement of the\n * aborted promise is still swallowed so it cannot surface as an unhandled\n * rejection.\n */\n\nimport type { ElementSchema, InferenceClient, InferenceResponse, StructuredResponse } from '@semiont/inference';\nimport type { Logger } from '@semiont/core';\nimport { withSpan } from '@semiont/observability';\n\n/**\n * Generous single-call bound. Slow local models on large prompts run\n * minutes, not tens of minutes; the stall watchdog (P3) sits above\n * this at 15 minutes, and the gateway's dead-worker janitor above\n * that at 30. Fixed by design — no env knob.\n */\nexport const INFERENCE_TIMEOUT_MS = 10 * 60_000;\n\n/**\n * The bound's own rejection, typed so classification (ABANDONED-INFERENCE\n * P3) never string-matches our own error message. A timeout says nothing\n * about the request — it classifies transient.\n */\nexport class InferenceTimeoutError extends Error {\n override readonly name = 'InferenceTimeoutError';\n}\n\n/**\n * How often an in-flight call reports that it is still alive\n * (DETECTION-HEARTBEAT D2).\n *\n * Detection's other liveness signal — the chunk-boundary heartbeat — emits\n * `N − 1` events for `N` chunks, which is ZERO for the single-chunk case that\n * every realistic document falls into (the derived input budget is ~935 K\n * tokens). A 7-minute call then emits nothing at all, and the client's\n * *inter-emission* timeout (`mark-state-unit`, 180 s) kills a perfectly\n * healthy job.\n *\n * Sized against that consumer: 15 s gives ~12 beats of margin inside the\n * 180 s window. Fixed by design, like the bound above — no env knob.\n */\nexport const INFERENCE_HEARTBEAT_MS = 15_000;\n\n/**\n * Called while a provider call is still in flight. Liveness only — the\n * caller repeats its current stage rather than inventing an advancing\n * percentage (D3): nothing here knows how far a single model call has got.\n */\nexport type InferenceHeartbeat = () => void;\n\n/**\n * One span per provider call. Before this, a 411-second detection job was a\n * SINGLE span with no children on a fully instrumented stack — it was not\n * possible to tell extraction from inference from telemetry, which is what\n * made the sibling silent-empty bug expensive to find. Attributes stay to\n * what is known before the answer arrives; token counts are recorded by\n * `recordInferenceUsage` in the client.\n */\nfunction spanned<T>(client: InferenceClient, kind: string, maxTokens: number, work: () => Promise<T>): Promise<T> {\n return withSpan(`inference:${kind}`, work, {\n attrs: {\n 'inference.provider': client.type,\n 'inference.model': client.modelId,\n 'inference.max_tokens': maxTokens,\n },\n });\n}\n\nasync function withTimeout<T>(\n work: (signal: AbortSignal) => Promise<T>,\n meta: { provider: string; model: string; label: string },\n onHeartbeat?: InferenceHeartbeat,\n logger?: Logger,\n): Promise<T> {\n const controller = new AbortController();\n let timer!: ReturnType<typeof setTimeout>;\n const timedOut = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n // True cancellation: tear the request down at the transport so it\n // cannot keep running (and billing) against a job that no longer\n // exists — and name the abort in the log, because an invisible\n // abandonment is what let a zombie burn 24+ minutes unrecorded (D3).\n logger?.warn('Aborting in-flight inference call at the timeout bound', {\n provider: meta.provider,\n model: meta.model,\n label: meta.label,\n boundMs: INFERENCE_TIMEOUT_MS,\n });\n controller.abort();\n reject(new InferenceTimeoutError(\n `Inference call timed out after ${INFERENCE_TIMEOUT_MS / 60_000} minutes (${meta.label}) — failing the job to keep the claim loop live`,\n ));\n }, INFERENCE_TIMEOUT_MS);\n timer.unref?.();\n });\n\n // One timer at one site covers every provider call, present and future —\n // putting it in the detection loops instead would re-couple liveness to\n // detection's own structure, which is the coupling this exists to undo.\n let heartbeat: ReturnType<typeof setInterval> | undefined;\n if (onHeartbeat) {\n heartbeat = setInterval(() => {\n try {\n onHeartbeat();\n } catch {\n // A failing progress emit must never take down the inference call\n // it is merely reporting on.\n }\n }, INFERENCE_HEARTBEAT_MS);\n heartbeat.unref?.();\n }\n\n const pending = work(controller.signal);\n try {\n return await Promise.race([pending, timedOut]);\n } catch (err) {\n // The aborted call settles promptly now (AbortError from the transport)\n // rather than minutes later — but its rejection still lands after the\n // race is lost, so it is still swallowed here to keep it from surfacing\n // as an unhandled one and killing the process.\n pending.catch(() => {});\n throw err;\n } finally {\n clearTimeout(timer);\n // Cleared with the timeout, in the same finally: a leaked interval would\n // beat forever on a completed job.\n if (heartbeat) clearInterval(heartbeat);\n }\n}\n\nexport function boundedGenerateWithMetadata(\n client: InferenceClient,\n prompt: string,\n maxTokens: number,\n temperature: number,\n onHeartbeat?: InferenceHeartbeat,\n logger?: Logger,\n): Promise<InferenceResponse> {\n return spanned(client, 'text', maxTokens, () => withTimeout(\n (signal) => client.generateTextWithMetadata(prompt, maxTokens, temperature, signal),\n { provider: client.type, model: client.modelId, label: `${client.type}:${client.modelId}` },\n onHeartbeat,\n logger,\n ));\n}\n\nexport function boundedGenerateStructured<T>(\n client: InferenceClient,\n prompt: string,\n maxTokens: number,\n temperature: number,\n elementSchema: ElementSchema,\n onHeartbeat?: InferenceHeartbeat,\n logger?: Logger,\n): Promise<StructuredResponse<T>> {\n return spanned(client, 'structured', maxTokens, () => withTimeout(\n (signal) => client.generateStructured<T>(prompt, maxTokens, temperature, elementSchema, signal),\n { provider: client.type, model: client.modelId, label: `${client.type}:${client.modelId}` },\n onHeartbeat,\n logger,\n ));\n}\n","/**\n * Failure classification — ABANDONED-INFERENCE P3 (A4, HD2).\n *\n * A retried deterministic failure always costs exactly double: the second\n * attempt of the same request cannot succeed. HD2's ruling is one-sided —\n * only KNOWN-deterministic failures skip the retry budget; everything\n * unrecognized stays retryable (`undefined`), because mis-classifying a\n * transient failure as deterministic silently halves reliability, while the\n * reverse merely costs one wasted attempt (today's behavior).\n *\n * Classification happens HERE, in the worker, where errors are still typed —\n * at the gateway's `job:fail` handler the failure is already a flattened\n * string, and message-regex classification is the drift this module exists\n * to avoid. The class rides the `job:fail` payload; `failJob` consumes it.\n */\n\nimport { isNumber, isObject, isString } from '@semiont/core';\nimport { StructuredReadError } from '@semiont/inference';\nimport { InferenceTimeoutError } from './workers/inference-call';\n\nexport type FailureClass = 'transient' | 'deterministic';\n\n/**\n * Marker for failures WE know cannot succeed on a second identical attempt —\n * thrown at the sites that judge the work itself rather than the transport:\n * response truncation despite the derived budget, a media type with nothing\n * to extract. Throw it instead of `Error` wherever that knowledge exists.\n */\nexport class DeterministicJobError extends Error {\n // Typed string, not the literal: subclasses (YieldCollapseError) carry\n // their own name — classification is instanceof, never name-matching.\n override readonly name: string = 'DeterministicJobError';\n}\n\n/**\n * The taxonomy, and its provider coupling — THE part that will drift:\n *\n * - `@anthropic-ai/sdk` errors carry a numeric `status`. 408/429/5xx are\n * environmental (throttles, overload, gateway weather) → transient. The\n * remaining 4xx mean the request itself was rejected — invalid, too\n * large, unauthorized — and re-sending it unchanged is guaranteed waste\n * → deterministic.\n * - Aborts (`APIUserAbortError` from the SDK, `AbortError` from fetch/mock)\n * are our own bound or shutdown tearing the transport down — nothing was\n * judged → transient.\n * - Ollama surfaces plain `Error`s (no status) and network failures as\n * `TypeError: fetch failed`; the SDK's `MessageStream terminated` is a\n * dropped connection. All land `undefined` → retryable, the safe default.\n * - `@semiont/inference`'s `StructuredReadError` carries the stop reason\n * because the cause classifies differently: `max_tokens` is truncation of\n * an over-demanded answer — the adapter throws before the caller's\n * `assertNotTruncated` can see the stop reason, so the classification\n * must happen on the typed error itself (measured live 2026-09-02) —\n * while any other reason is model misbehavior a retry may fix.\n */\nexport function classifyFailure(error: unknown): FailureClass | undefined {\n if (error instanceof DeterministicJobError) return 'deterministic';\n if (error instanceof InferenceTimeoutError) return 'transient';\n if (error instanceof StructuredReadError && error.stopReason === 'max_tokens') return 'deterministic';\n if (!isObject(error)) return undefined;\n\n const name = isString(error.name) ? error.name : '';\n if (name === 'DeterministicJobError') return 'deterministic';\n if (name === 'APIUserAbortError' || name === 'AbortError') return 'transient';\n\n const status = isNumber(error.status) ? error.status : undefined;\n if (status !== undefined) {\n if (status === 408 || status === 429 || status >= 500) return 'transient';\n if (status >= 400) return 'deterministic';\n }\n\n return undefined;\n}\n","/**\n * Detection budget derivation — pure window arithmetic over the inference\n * provider's published limits. No hand-tuned chunk constants, no density or\n * yield modeling: document content never enters this arithmetic. The guard\n * for the pathological tail (a chunk whose annotation JSON still overflows\n * the output budget) is `assertNotTruncated` below — invoked per chunk by\n * the callers on every response, not a prediction here.\n *\n * Provider shapes (see `@semiont/inference` interface.ts):\n * - Shared window (Ollama): the provider publishes\n * `maxOutputTokens === contextTokens` — prompt and response share one\n * `num_ctx`. What remains after the prompt scaffold is split\n * input:output = 1:2 (annotation JSON echoes each span plus a fixed\n * key/context envelope, so output needs the larger share). The 1:2 ratio\n * is the plan's one allocation policy — doc-independent, tuned only on\n * live evidence.\n * - Separate ceilings (Anthropic): output takes its full ceiling (duration-\n * capped below), and input follows the same 1:2 allocation — never more\n * than half the output budget. Input does NOT get \"the rest of the\n * window\": measured 2026-09-02, a window-sized chunk of entity-dense text\n * demands more output than any budget holds, so the model grinds toward\n * max_tokens for minutes (killed at the call bound as a \"stall\") or\n * collapses to the degenerate []. Large documents chunk; that is the fix,\n * not a cost.\n */\n\nimport { chunkText, type ChunkingConfig, type Logger } from '@semiont/core';\nimport { StructuredReadError, type InferenceLimits, type TokenUsage } from '@semiont/inference';\nimport { recordDetectionCall } from '@semiont/observability';\nimport { DeterministicJobError } from '../../failure-class';\nimport { INFERENCE_TIMEOUT_MS, InferenceTimeoutError } from '../inference-call';\n\n/**\n * A `max_tokens` stop reason means the model's JSON was cut off mid-stream.\n * Structured output serializes whatever was generated, so that still yields\n * a syntactically-valid but incomplete array — it would parse cleanly and\n * silently under-report. Fail the job loudly instead. With derived budgets\n * this fires only on pathological annotation density.\n *\n * ONE decider for every detection path (entity extraction and the four\n * motivations) — the classification must not diverge between them: same\n * input truncates the same way, so a retry is guaranteed waste and the\n * throw carries the deterministic class (ABANDONED-INFERENCE P3, A4).\n */\nexport function assertNotTruncated(response: { stopReason: string }, label: string, chunk: number, totalChunks: number, outputBudget: number): void {\n if (response.stopReason === 'max_tokens') {\n throw new DeterministicJobError(`${label} response truncated (max_tokens) on chunk ${chunk}/${totalChunks} despite the derived output budget of ${outputBudget} tokens — failing the job rather than under-reporting annotations.`);\n }\n}\n\n/**\n * `reconcileSelector` disambiguates a span with up to 64 chars of prefix and\n * 64 of suffix (the annotation-selector schema). Overlap must let a span\n * sitting at a chunk boundary carry that context — plus a span allowance of\n * the same order — into the adjacent chunk. Schema-derived, not tuned.\n */\nconst SELECTOR_CONTEXT_CHARS = 64;\nconst OVERLAP_CHARS =\n SELECTOR_CONTEXT_CHARS + // prefix\n SELECTOR_CONTEXT_CHARS + // suffix\n 2 * SELECTOR_CONTEXT_CHARS; // span allowance\n/** ~4 chars/token — the same heuristic `estimateTokens`/`chunkText` use. */\nconst OVERLAP_TOKENS = Math.ceil(OVERLAP_CHARS / 4);\n\n/**\n * One temperature for every detection call. Detection copies spans verbatim\n * against a closed type vocabulary — a fidelity task, so determinism is the\n * right default (reproducible re-runs). Measured equal to hotter settings\n * on yield and consistency at production call sizes.\n */\nexport const DETECTION_TEMPERATURE = 0;\n\n/**\n * Assumed worst-case output rate for providers that publish none\n * (OLLAMA-DETECTION-TESTING P3b, from F9). Rate-silent providers (Ollama —\n * local hardware, rate unknowable a priori) previously got NO duration bound:\n * capacity-sizing handed a 262K-window model a ~174K-token output budget, and\n * a repetition loop then burned the full 10-minute guillotine as TRANSIENT —\n * retried identically. Under a duration-shaped cap the same loop dies in\n * minutes as max_tokens → deterministic → subdividable: the useful failure.\n *\n * 30 tok/s. P2 measured 36–90 tok/s across three local model families\n * (qwen3.5:9b ~36, gemma4:26b ~50, gemma4:e2b ~90), and the half-bound rule\n * in the cap below means real rates down to 15 tok/s still finish inside the\n * guillotine — anything slower is wedged, not working, which is exactly what\n * the guillotine is for. A conservative floor, deliberately NOT per-model:\n * F8 showed per-model thresholds chase noise. Owned policy constant, same\n * status as the 1:2 split and DETECTION_TEMPERATURE.\n */\nexport const ASSUMED_OUTPUT_TOKENS_PER_HOUR = 108_000;\n\n/**\n * The count-verifier band (OLLAMA-DETECTION-TESTING P3c, user-ratified\n * 2026-09-05): an extraction that found fewer than 1/BAND of the mentions a\n * cheap count call reports is flagged as silent yield collapse (F7). The band\n * is LOAD-BEARING at 2: F12 measured the same model flipping between\n * entity-like and mention-like enumeration on the same corpus, and that\n * legitimate judgment spread must fit INSIDE the band — probe evidence (n=6)\n * put every healthy ratio at 0.68–0.88 and every collapse at 0.18–0.41.\n */\nexport const YIELD_COLLAPSE_BAND = 2;\n\n/**\n * The silent-yield-collapse verdict (F7): a schema-clean, done-reason-clean\n * extraction that found a fraction of what a cheap count call says is present.\n * Extends DeterministicJobError because the collapse is MEASURED deterministic\n * — bit-identical across retries and across budget regimes — so a retry is\n * guaranteed waste; the classification and the size-floored subdivision\n * descent both follow from the base class. It diverges from truncation at the\n * size floor: NO re-roll (a same-size re-roll provably returns the identical\n * collapse), and — ruled 2026-09-05 after P4 attempt 1, amending the original\n * fail-the-job invariant — the floor ACCEPTS the flagged piece's `salvage`\n * loudly rather than failing the unit: one hostile ~530-char stretch had\n * discarded ~20 chunks of good extraction, and at floor sizes the count's\n * evidence sits far below anything the probe validated. The warning and the\n * 'collapsed' telemetry rows are the durable record; re-detection heals.\n */\nexport class YieldCollapseError extends DeterministicJobError {\n override readonly name = 'YieldCollapseError';\n /** What the flagged extraction DID find — every span write-time-verified,\n * so discarding it at the floor would add loss on top of the under-report.\n * Carried on the error because the flag site cannot know whether descent\n * remains possible; the floor is the subdivider's knowledge. */\n constructor(message: string, readonly salvage: unknown[] = []) {\n super(message);\n }\n}\n\nexport interface DetectionBudget {\n /** Feed to `chunkText` — `chunkSize` is the derived input budget (tokens). */\n chunking: ChunkingConfig;\n /** Pass as `maxTokens` on every per-chunk inference call. */\n outputBudget: number;\n}\n\n/**\n * Derive the per-call input/output token budgets for a detection job from the\n * provider's actual limits and the measured prompt scaffold.\n *\n * @param limits - the provider's published ceilings (`client.limits()`)\n * @param scaffoldTokens - tokens of the prompt template around the content,\n * measured from the actually-built template (e.g. `estimateTokens(build(''))`)\n * @param typesPerCall - how many entity types (or equivalent span families)\n * ONE call asks for: output demand scales with it, so the allocation\n * divides by it. The per-type loop passes 1; a future multi-type batch\n * passes its batch size.\n * @throws when the window is too small to hold the scaffold plus a useful\n * chunk — fail-loud, same family as the truncation and window guards.\n */\nexport function deriveDetectionBudget(\n limits: InferenceLimits,\n scaffoldTokens: number,\n typesPerCall: number,\n): DetectionBudget {\n const { contextTokens, maxOutputTokens } = limits;\n const available = contextTokens - scaffoldTokens;\n\n let inputBudget: number;\n let outputBudget: number;\n\n if (maxOutputTokens >= contextTokens) {\n // Shared window: 1:2 input:output split of what the scaffold leaves.\n inputBudget = Math.floor(available / 3);\n outputBudget = available - inputBudget;\n } else {\n outputBudget = maxOutputTokens;\n inputBudget = contextTokens - outputBudget - scaffoldTokens;\n if (inputBudget <= 0) {\n // Degenerate separate-ceilings shape (output ceiling nearly fills the\n // window): fall back to the shared split rather than starving input.\n inputBudget = Math.floor(available / 3);\n outputBudget = available - inputBudget;\n }\n }\n\n // Duration floor (ABANDONED-INFERENCE P4 HD3; universalized by\n // OLLAMA-DETECTION-TESTING P3b): cap each call's output at what the\n // provider's worst-case rate finishes inside our own inference bound — a\n // call projected past the 10-minute guillotine is planned-to-fail — and\n // scale input by the same factor, so the input:output ratio (and with it\n // the per-chunk truncation risk profile) is exactly the capacity\n // solution's. Capacity says what CAN fit in one call; this says what\n // SHOULD. EVERY provider gets the bound: the published rate when there is\n // one, the conservative assumed floor when there is not — a rate-silent\n // provider with no bound turned repetition loops into hour-long transient\n // burns (F9; see ASSUMED_OUTPUT_TOKENS_PER_HOUR). It is a floor on chunk\n // count, never a raise (a tighter capacity budget is left alone). Side\n // effect worth knowing: on Anthropic this lands every detection call at\n // or under the SDK's non-streaming threshold — off the MessageStream path\n // the original `terminated` failure arrived on.\n const outputTokensPerHour = limits.outputTokensPerHour ?? ASSUMED_OUTPUT_TOKENS_PER_HOUR;\n {\n // Half the bound, not all of it: at the full bound the slowest\n // legitimate full-budget generation lands exactly on the guillotine.\n // At half, the guillotine fires only on calls at least 2× slower than\n // the provider's own worst-case model — wedged, not working.\n const durationSafeOutput = Math.floor(outputTokensPerHour * (INFERENCE_TIMEOUT_MS / 2) / 3_600_000);\n if (outputBudget > durationSafeOutput) {\n inputBudget = Math.floor(inputBudget * (durationSafeOutput / outputBudget));\n outputBudget = durationSafeOutput;\n }\n }\n\n // The 1:2 allocation policy, applied to every provider shape (the shared\n // split satisfies it by construction; separate ceilings did not). Output\n // must hold an annotation echo of every span in the chunk plus its\n // key/context envelope, so a chunk larger than half the output budget is\n // a call whose honest answer cannot fit — the failure measured live on\n // 2026-09-02 (silent multi-minute grinds to max_tokens, degenerate []).\n // The demand is PER TYPE ASKED FOR, so a K-type call divides the input\n // share by K. Still no density modeling: same one policy, content never\n // enters.\n inputBudget = Math.min(inputBudget, Math.floor(outputBudget / (2 * typesPerCall)));\n\n if (inputBudget <= OVERLAP_TOKENS) {\n throw new Error(\n `Inference window too small for detection: context ${contextTokens} tokens minus scaffold ${scaffoldTokens} leaves an input budget of ${inputBudget} (need > ${OVERLAP_TOKENS}). Use a model with a larger context window or reduce the prompt scaffold.`,\n );\n }\n\n return {\n chunking: { chunkSize: inputBudget, overlap: OVERLAP_TOKENS },\n outputBudget,\n };\n}\n\n/**\n * Depth cap for TIMEOUTS only: a call still timing out on a quarter-sized\n * chunk is not a size problem, and timeouts classify transient — the\n * job-level retry is their second chance. Truncations descend by size\n * instead (see `callChunkSubdividing`).\n */\nexport const MAX_SUBDIVISION_DEPTH = 2;\n\n/** The failures a smaller chunk can plausibly fix. An unreadable response\n * that stopped naturally is model misbehavior, not size. */\n/** The F3 shape: unreadable output whose stop reason is UNKNOWN (done_reason\n * absent). Held not-subdividable through P3a for lack of evidence; the live\n * gate then reproduced it twice on real text (2026-09-03 at ~21K chars, P4\n * attempt 2 at ~4.5K, mid-descent, adjacent to max_tokens truncations that\n * healed by subdividing) and measured the retry-at-same-size alternative as a\n * deterministic 34 s burn. Size-shaped on the evidence — descends like\n * truncation, but with no floor re-roll (near-deterministic, nothing salvaged)\n * and unchanged retryable classification (a genuinely broken server still\n * deserves its budget). */\nfunction unknownUnreadable(error: unknown): boolean {\n return error instanceof StructuredReadError && error.stopReason === 'unknown';\n}\n\nfunction subdividable(error: unknown): boolean {\n return error instanceof InferenceTimeoutError || truncation(error) || unknownUnreadable(error);\n}\n\n/** Truncation in either surface: parsed-but-flagged (`assertNotTruncated`'s\n * `DeterministicJobError`) or cut off mid-JSON (`StructuredReadError` with\n * `max_tokens`). */\nfunction truncation(error: unknown): boolean {\n return (\n error instanceof DeterministicJobError ||\n (error instanceof StructuredReadError && error.stopReason === 'max_tokens')\n );\n}\n\n/**\n * Run one chunk's inference call, subdividing IN PLACE when it fails in a\n * way a smaller chunk can fix — instead of burning the whole attempt to\n * come back at the same size. Sub-pieces re-use the caller's overlap, so\n * spans straddling a split are caught twice and fall to the downstream\n * span-keyed dedupe. On a failure subdivision cannot fix, the ORIGINAL\n * error propagates so classification sees what actually happened.\n */\nexport interface ChunkCallResult<T> {\n items: T[];\n /** The provider's own token counts, when it reported any. Never estimated. */\n usage?: TokenUsage;\n}\n\n/** Which shape of failure this was — they demand opposite responses, so the\n * history must tell them apart: truncation descends by size, a timeout fails\n * fast, anything else is not size-shaped at all. */\nfunction outcomeOf(error: unknown): 'truncated' | 'timeout' | 'collapsed' | 'error' {\n // Collapse before truncation: it IS a DeterministicJobError (so the\n // truncation check would swallow it), but in the history the two are\n // different facts — one is output overflow, the other silent under-report.\n if (error instanceof YieldCollapseError) return 'collapsed';\n if (truncation(error)) return 'truncated';\n if (error instanceof InferenceTimeoutError) return 'timeout';\n return 'error';\n}\n\nexport async function callChunkSubdividing<T>(\n label: string,\n chunk: string,\n chunking: ChunkingConfig,\n call: (piece: string) => Promise<ChunkCallResult<T>>,\n logger?: Logger,\n): Promise<T[]> {\n // One telemetry record per model call, successes AND failures\n // (DETECTION-QUALITY-THROUGHPUT P1). This is the only place `depth` and\n // `reroll` exist, so it is the only place a complete record can be written.\n async function recorded(piece: string, depth: number, reroll: boolean): Promise<ChunkCallResult<T>> {\n const start = performance.now();\n try {\n const result = await call(piece);\n recordDetectionCall({\n label, pieceChars: piece.length, durationMs: performance.now() - start,\n items: result.items.length, depth, reroll, outcome: 'success',\n ...(result.usage ? { inputTokens: result.usage.inputTokens, outputTokens: result.usage.outputTokens } : {}),\n });\n return result;\n } catch (error) {\n // A failed call still cost its input and its wall time — the descent's\n // price is invisible without it.\n recordDetectionCall({\n label, pieceChars: piece.length, durationMs: performance.now() - start,\n items: 0, depth, reroll, outcome: outcomeOf(error),\n });\n throw error;\n }\n }\n\n async function attempt(piece: string, chunkSize: number, depth: number): Promise<T[]> {\n try {\n return (await recorded(piece, depth, false)).items;\n } catch (error) {\n if (!subdividable(error)) throw error;\n const half = Math.floor(chunkSize / 2);\n // Truncation descends by SIZE: demand halves with each subdivision,\n // so descent terminates — and list-dense text (registers, indexes)\n // honestly yields several times its input in annotation output, so\n // the descent must go as deep as the content demands. The floor\n // derives from the overlap constant; below it even solid names fit\n // the budget. Timeouts stay depth-capped and fail fast.\n //\n // A descent must also actually CHANGE the input: once a piece fits\n // inside the smaller chunk size, re-chunking returns it unchanged, and\n // at temperature 0 the identical call returns the identical failure —\n // measured live (P4 attempt 1: one 572-char piece \"descended\" through\n // three depths, same verdict each time). A piece that cannot shrink is\n // AT its floor, whatever the arithmetic floor says.\n const pieces = chunkText(piece, { chunkSize: half, overlap: chunking.overlap });\n const shrinks = pieces.length > 1 || pieces[0] !== piece;\n // Size-shaped failures (truncation, collapse, the F3 unknown-unreadable)\n // descend to the SIZE floor; only timeouts are depth-capped — the F3 fix\n // must reach failures that first appear mid-descent (measured at depth 3).\n const canDescend = shrinks && (truncation(error) || unknownUnreadable(error)\n ? half > 2 * OVERLAP_TOKENS\n : depth < MAX_SUBDIVISION_DEPTH);\n if (!canDescend) {\n // A collapse verdict at the floor is ACCEPTED, loudly (ruled\n // 2026-09-05): its salvage flows through with a warning instead of\n // one hostile piece discarding the whole unit's work. No re-roll —\n // the collapse is deterministic, a same-size retry changes nothing.\n if (error instanceof YieldCollapseError) {\n logger?.warn('Floor-size piece still flagged as collapsed — accepting its under-reported salvage and continuing', {\n pieceChars: piece.length,\n salvaged: error.salvage.length,\n error: error.message,\n });\n return error.salvage as T[];\n }\n // At the size floor honest overflow is impossible, so truncation\n // here is a degeneration loop — a sampling accident. One same-size\n // re-roll; a second truncation propagates. Timeouts get no re-roll.\n if (!truncation(error)) throw error;\n logger?.warn('Floor-size piece truncated — re-rolling once before giving up', {\n pieceChars: piece.length,\n error: error instanceof Error ? error.message : String(error),\n });\n return (await recorded(piece, depth, true)).items;\n }\n logger?.warn('Chunk call failed at a size-shaped bound — subdividing and retrying smaller', {\n depth: depth + 1,\n pieceChars: piece.length,\n nextChunkSizeTokens: half,\n error: error instanceof Error ? error.message : String(error),\n });\n const collected: T[] = [];\n for (const p of pieces) {\n collected.push(...(await attempt(p, half, depth + 1)));\n }\n return collected;\n }\n }\n return attempt(chunk, chunking.chunkSize, 0);\n}\n","/**\n * Prompt builders for annotation detection motivations\n *\n * Provides static methods to build AI prompts for each Web Annotation motivation type.\n * Extracted from worker implementations to centralize prompt logic.\n *\n * Locale handling: builders take two optional BCP-47 tags. `language` is the\n * locale the *generated body text* should be written in (sourced from the user's\n * UI locale); `sourceLanguage` is the locale of the *text being analyzed*\n * (sourced from `ResourceDescriptor` / its primary representation). They are\n * independent — see `types.ts` \"Locale conventions\" for the full discussion.\n */\n\nimport { getLocaleEnglishName } from '@semiont/core';\n\nfunction languageName(tag: string): string {\n return getLocaleEnglishName(tag) || tag;\n}\n\n/** Returns \"Source text language: <Name>.\" or empty string. */\nfunction sourceLanguageGuidance(sourceLanguage?: string): string {\n if (!sourceLanguage) return '';\n return `\\n\\nSource text language: ${languageName(sourceLanguage)}.`;\n}\n\n/** Returns \"Write your <kind> in <Name>.\" or empty string. */\nfunction bodyLanguageGuidance(language: string | undefined, kind: string): string {\n if (!language || language === 'en') return '';\n return `\\n\\nIMPORTANT: Write your ${kind} in ${languageName(language)}.`;\n}\n\nexport class MotivationPrompts {\n /**\n * Build a prompt for detecting comment-worthy passages\n *\n * @param content - The text content to analyze — a chunk sized by the\n * caller from derived provider limits; NEVER re-truncated here (a\n * builder-level clip is silent input loss — see #738)\n * @param instructions - Optional user-provided instructions\n * @param tone - Optional tone guidance (e.g., \"academic\", \"conversational\")\n * @param density - Optional target number of comments per 2000 words\n * @returns Formatted prompt string\n */\n static buildCommentPrompt(\n content: string,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string\n ): string {\n let prompt: string;\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n const bodyLang = bodyLanguageGuidance(language, 'comments');\n\n if (instructions) {\n // User provided specific instructions - minimal prompt, let instructions drive behavior\n const toneGuidance = tone ? ` Use a ${tone} tone.` : '';\n const densityGuidance = density\n ? `\\n\\nAim for approximately ${density} comments per 2000 words of text.`\n : ''; // Let user instructions determine density\n\n prompt = `Add comments to passages in this text following these instructions:\n\n${instructions}${toneGuidance}${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of comments. Each comment must have:\n- \"exact\": the exact text passage being commented on (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"comment\": your comment following the instructions above\n\nRespond with a valid JSON array.\n\nExample:\n[\n {\"exact\": \"the quarterly review meeting\", \"prefix\": \"We need to schedule \", \"suffix\": \" for next month.\", \"comment\": \"Who will lead this? Should we invite the external auditors?\"}\n]`;\n } else {\n // No specific instructions - fall back to explanatory/educational mode\n const toneGuidance = tone\n ? `\\n\\nTone: Use a ${tone} style in your comments.`\n : '';\n const densityGuidance = density\n ? `\\n- Aim for approximately ${density} comments per 2000 words`\n : `\\n- Aim for 3-8 comments per 2000 words (not too sparse or dense)`;\n\n prompt = `Identify passages in this text that would benefit from explanatory comments.\nFor each passage, provide contextual information, clarification, or background.${toneGuidance}\n\nGuidelines:\n- Select passages that reference technical terms, historical figures, complex concepts, or unclear references\n- Provide comments that ADD VALUE beyond restating the text\n- Focus on explanation, background, or connections to other ideas\n- Avoid obvious or trivial comments\n- Keep comments concise (1-3 sentences typically)${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of comments. Each comment should have:\n- \"exact\": the exact text passage being commented on (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"comment\": your explanatory comment (1-3 sentences, provide context/background/clarification)\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"Ouranos\", \"prefix\": \"In the beginning, \", \"suffix\": \" ruled the universe\", \"comment\": \"Ouranos (also spelled Uranus) is the primordial Greek deity personifying the sky. In Hesiod's Theogony, he is the son and husband of Gaia (Earth) and father of the Titans.\"}\n]`;\n }\n\n return prompt;\n }\n\n /**\n * Build a prompt for detecting highlight-worthy passages\n *\n * @param content - The text content to analyze — a chunk sized by the\n * caller from derived provider limits; NEVER re-truncated here (a\n * builder-level clip is silent input loss — see #738)\n * @param instructions - Optional user-provided instructions\n * @param density - Optional target number of highlights per 2000 words\n * @returns Formatted prompt string\n */\n static buildHighlightPrompt(\n content: string,\n instructions?: string,\n density?: number,\n sourceLanguage?: string\n ): string {\n let prompt: string;\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n\n if (instructions) {\n // User provided specific instructions - minimal prompt, let instructions drive behavior\n const densityGuidance = density\n ? `\\n\\nAim for approximately ${density} highlights per 2000 words of text.`\n : ''; // Let user instructions determine density\n\n prompt = `Identify passages in this text to highlight following these instructions:\n\n${instructions}${densityGuidance}${sourceLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of highlights. Each highlight must have:\n- \"exact\": the exact text passage to highlight (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n\nRespond with a valid JSON array.\n\nExample:\n[\n {\"exact\": \"revenue grew 45% year-over-year\", \"prefix\": \"In Q3 2024, \", \"suffix\": \", exceeding all forecasts.\"}\n]`;\n } else {\n // No specific instructions - fall back to importance/salience mode\n const densityGuidance = density\n ? `\\n- Aim for approximately ${density} highlights per 2000 words`\n : `\\n- Aim for 3-8 highlights per 2000 words (be selective)`;\n\n prompt = `Identify passages in this text that merit highlighting for their importance or salience.\nFocus on content that readers should notice and remember.\n\nGuidelines:\n- Highlight key claims, findings, or conclusions\n- Highlight important definitions, terminology, or concepts\n- Highlight notable quotes or particularly striking statements\n- Highlight critical decisions, action items, or turning points\n- Select passages that are SIGNIFICANT, not just interesting\n- Avoid trivial or obvious content${densityGuidance}${sourceLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of highlights. Each highlight should have:\n- \"exact\": the exact text passage to highlight (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"we will discontinue support for legacy systems by March 2025\", \"prefix\": \"After careful consideration, \", \"suffix\": \". This decision affects\"}\n]`;\n }\n\n return prompt;\n }\n\n /**\n * Build a prompt for detecting assessment-worthy passages\n *\n * @param content - The text content to analyze — a chunk sized by the\n * caller from derived provider limits; NEVER re-truncated here (a\n * builder-level clip is silent input loss — see #738)\n * @param instructions - Optional user-provided instructions\n * @param tone - Optional tone guidance (e.g., \"critical\", \"supportive\")\n * @param density - Optional target number of assessments per 2000 words\n * @returns Formatted prompt string\n */\n static buildAssessmentPrompt(\n content: string,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string\n ): string {\n let prompt: string;\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n const bodyLang = bodyLanguageGuidance(language, 'assessments');\n\n if (instructions) {\n // User provided specific instructions - minimal prompt, let instructions drive behavior\n const toneGuidance = tone ? ` Use a ${tone} tone.` : '';\n const densityGuidance = density\n ? `\\n\\nAim for approximately ${density} assessments per 2000 words of text.`\n : ''; // Let user instructions determine density\n\n prompt = `Assess passages in this text following these instructions:\n\n${instructions}${toneGuidance}${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of assessments. Each assessment must have:\n- \"exact\": the exact text passage being assessed (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"assessment\": your assessment following the instructions above\n\nRespond with a valid JSON array.\n\nExample:\n[\n {\"exact\": \"the quarterly revenue target\", \"prefix\": \"We established \", \"suffix\": \" for Q4 2024.\", \"assessment\": \"This target seems ambitious given market conditions. Consider revising based on recent trends.\"}\n]`;\n } else {\n // No specific instructions - fall back to analytical/evaluation mode\n const toneGuidance = tone\n ? `\\n\\nTone: Use a ${tone} style in your assessments.`\n : '';\n const densityGuidance = density\n ? `\\n- Aim for approximately ${density} assessments per 2000 words`\n : `\\n- Aim for 2-6 assessments per 2000 words (focus on key passages)`;\n\n prompt = `Identify passages in this text that merit critical assessment or evaluation.\nFor each passage, provide analysis of its validity, strength, or implications.${toneGuidance}\n\nGuidelines:\n- Select passages containing claims, arguments, conclusions, or assertions\n- Assess evidence quality, logical soundness, or practical implications\n- Provide assessments that ADD INSIGHT beyond restating the text\n- Focus on passages where evaluation would help readers form judgments\n- Keep assessments concise yet substantive (1-3 sentences typically)${densityGuidance}${sourceLang}${bodyLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of assessments. Each assessment should have:\n- \"exact\": the exact text passage being assessed (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n- \"assessment\": your analytical assessment (1-3 sentences, evaluate validity/strength/implications)\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"AI will replace most jobs by 2030\", \"prefix\": \"Many experts predict that \", \"suffix\": \", fundamentally reshaping\", \"assessment\": \"This claim lacks nuance and supporting evidence. Employment patterns historically show job transformation rather than wholesale replacement. The timeline appears speculative without specific sector analysis.\"}\n]`;\n }\n\n return prompt;\n }\n\n /**\n * Build a prompt for detecting structural tags\n *\n * @param content - The full text content to analyze (NOT truncated for structural analysis)\n * @param category - The specific category to detect\n * @param schemaName - Human-readable schema name\n * @param schemaDescription - Schema description\n * @param schemaDomain - Schema domain\n * @param categoryDescription - Category description\n * @param categoryExamples - Example questions/guidance for this category\n * @returns Formatted prompt string\n */\n static buildTagPrompt(\n content: string,\n category: string,\n schemaName: string,\n schemaDescription: string,\n schemaDomain: string,\n categoryDescription: string,\n categoryExamples: string[],\n sourceLanguage?: string\n ): string {\n // Tags are schema-defined identifiers (\"Issue\", \"Rule\") — the LLM doesn't\n // generate the category label, so there's no body-language guidance here.\n // Only `sourceLanguage` is wired into the prompt; `language` (body locale)\n // is consumed at the body-stamp site in `processors.ts`.\n const sourceLang = sourceLanguageGuidance(sourceLanguage);\n\n const prompt = `You are analyzing a text using the ${schemaName} framework.\n\nSchema: ${schemaDescription}\nDomain: ${schemaDomain}\n\nYour task: Identify passages that serve the structural role of \"${category}\".\n\nCategory: ${category}\nDescription: ${categoryDescription}\nKey questions:\n${categoryExamples.map(ex => `- ${ex}`).join('\\n')}\n\nGuidelines:\n- Focus on STRUCTURAL FUNCTION, not semantic content\n- A passage serves the \"${category}\" role if it performs this function in the document's structure\n- Look for passages that explicitly fulfill this role\n- Passages can be sentences, paragraphs, or sections\n- Aim for precision - only tag passages that clearly serve this structural role\n- Typical documents have 1-5 instances of each category (some may have 0)${sourceLang}\n\nText to analyze:\n---\n${content}\n---\n\nReturn a JSON array of tags. Each tag should have:\n- \"exact\": the exact text passage (quoted verbatim from source)\n- \"prefix\": up to 64 characters of text immediately before the passage\n- \"suffix\": up to 64 characters of text immediately after the passage\n\nRespond with a valid JSON array.\n\nExample format:\n[\n {\"exact\": \"What duty did the defendant owe?\", \"prefix\": \"The central question is: \", \"suffix\": \" This question must be\"},\n {\"exact\": \"In tort law, a duty of care is established when...\", \"prefix\": \"Legal framework:\\\\n\", \"suffix\": \"\\\\n\\\\nApplying this standard\"}\n]`;\n\n return prompt;\n }\n}\n","import type { AnchorMethod, Logger } from '@semiont/core';\nimport { recordAnchorOutcome } from '@semiont/observability';\n\n/**\n * The anchoring methods that picked a plausible occurrence rather than a\n * certain one: `first-of-many` had several candidates and no usable context,\n * `fuzzy-match` recovered through case/whitespace/Levenshtein. Neither is an\n * error — the write-time selector invariant still holds — but both are places\n * the span could be the wrong instance.\n *\n * ONE decider, deliberately. This classification lived in two places (the\n * reference path's inline warn and the motivation parsers' logger) that\n * happened to agree; two copies of a risk judgement is one copy too many.\n */\nconst DEGRADED: ReadonlySet<string> = new Set<AnchorMethod>(['first-of-many', 'fuzzy-match']);\n\n/**\n * Audit one anchoring outcome: counted always, warned only when degraded.\n *\n * Counting EVERY outcome is the point (P5). A warning tells an operator that\n * one anchor was uncertain; only a rate over all anchors says whether the\n * detection run was precise, and the rate is what the plan asks to put beside\n * the yield numbers.\n */\nexport function noteAnchor(label: string, exact: string, method: AnchorMethod, logger?: Logger): void {\n recordAnchorOutcome(label, method);\n if (!DEGRADED.has(method)) return;\n const detail = { text: exact, anchorMethod: method };\n if (logger) logger.warn('Annotation anchored via degraded method', { label, ...detail });\n else console.warn(`[${label}] anchored via ${method}: \"${exact}\"`);\n}\n","/**\n * Response parsers for annotation detection motivations\n *\n * Provides static methods to parse and validate AI responses for each motivation type.\n * Includes offset validation and correction logic.\n * Extracted from worker implementations to centralize parsing logic.\n *\n * NOTE: These are static utility methods without logger access.\n * Console statements kept for debugging - consider adding logger parameter in future.\n */\n\nimport { reconcileSelector, isObject, isString, type AnchorMethod } from '@semiont/core';\nimport type { ElementSchema } from '@semiont/inference';\nimport { noteAnchor } from './anchor-audit';\n\n// Parsers receive ALREADY-PARSED elements (`unknown[]`) from the structured\n// inference surface — `generateStructured` returns `T[]` or throws, so\n// \"could not read the model\" never reaches this layer and there is no string\n// to parse here (the former strict `parseJsonArray` moved into that contract,\n// as the earlier tolerant walker moved into it before). What remains here is\n// per-element structural validation (STRUCTURED-INFERENCE D5 — the last line\n// on the Ollama path and the schema/type drift guard) plus reconciliation\n// against the full document.\n//\n// Each element schema is declared ADJACENT to the Match interface it mirrors:\n// the schema constrains the wire, the interface is what the code consumes,\n// and nothing verifies they agree — adjacency is the drift guard.\n// `prefix`/`suffix` stay OUT of `required` deliberately: requiring them turns\n// \"sometimes absent\" into \"always present, sometimes empty\" (measured\n// 2026-08-06), an anchoring-path change Phase 3 examines before anyone\n// relies on it.\n\n/**\n * Represents a detected comment with validated position\n */\nexport interface CommentMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n comment: string;\n}\n\n/** Wire schema for one comment element — keep in lockstep with `CommentMatch`. */\nexport const COMMENT_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n comment: { type: 'string' },\n },\n required: ['exact', 'comment'],\n additionalProperties: false,\n};\n\n/**\n * Represents a detected highlight with validated position\n */\nexport interface HighlightMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n}\n\n/** Wire schema for one highlight element — keep in lockstep with `HighlightMatch`. */\nexport const HIGHLIGHT_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n },\n required: ['exact'],\n additionalProperties: false,\n};\n\n/**\n * Represents a detected assessment with validated position\n */\nexport interface AssessmentMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n assessment: string;\n}\n\n/** Wire schema for one assessment element — keep in lockstep with `AssessmentMatch`. */\nexport const ASSESSMENT_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n assessment: { type: 'string' },\n },\n required: ['exact', 'assessment'],\n additionalProperties: false,\n};\n\n/**\n * Represents a detected tag with validated position\n */\nexport interface TagMatch {\n exact: string;\n start: number;\n end: number;\n prefix?: string;\n suffix?: string;\n category: string;\n}\n\n/**\n * Wire schema for one tag element — keep in lockstep with `RawTagInput`\n * (the category is stamped by the caller, not emitted by the model).\n */\nexport const TAG_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n },\n required: ['exact'],\n additionalProperties: false,\n};\n\nexport class MotivationParsers {\n /**\n * Validate and reconcile structured comment elements.\n *\n * @param parsed - Already-parsed elements from the structured surface\n * @param content - Original content to validate offsets against\n * @returns Array of validated comment matches\n */\n static parseComments(parsed: unknown[], content: string): CommentMatch[] {\n\n const valid = parsed.filter((c): c is { exact: string; prefix?: string; suffix?: string; comment: string } =>\n isObject(c) &&\n isString(c.exact) &&\n isString(c.comment) &&\n c.comment.trim().length > 0\n );\n\n console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);\n\n const validatedComments: CommentMatch[] = [];\n for (const comment of valid) {\n const reconciled = reconcileSelector(content, {\n exact: comment.exact,\n ...(typeof comment.prefix === 'string' ? { prefix: comment.prefix } : {}),\n ...(typeof comment.suffix === 'string' ? { suffix: comment.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated comment \"${comment.exact}\"`);\n continue;\n }\n logAnchorMethod('comment', comment.exact, reconciled.anchorMethod);\n validatedComments.push({\n comment: comment.comment,\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n\n return validatedComments;\n }\n\n /**\n * Validate and reconcile structured highlight elements.\n *\n * @param parsed - Already-parsed elements from the structured surface\n * @param content - Original content to validate offsets against\n * @returns Array of validated highlight matches\n */\n static parseHighlights(parsed: unknown[], content: string): HighlightMatch[] {\n\n const highlights = parsed.filter((h): h is { exact: string; prefix?: string; suffix?: string } =>\n isObject(h) && isString(h.exact)\n );\n\n const validatedHighlights: HighlightMatch[] = [];\n for (const highlight of highlights) {\n const reconciled = reconcileSelector(content, {\n exact: highlight.exact,\n ...(typeof highlight.prefix === 'string' ? { prefix: highlight.prefix } : {}),\n ...(typeof highlight.suffix === 'string' ? { suffix: highlight.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated highlight \"${highlight.exact}\"`);\n continue;\n }\n logAnchorMethod('highlight', highlight.exact, reconciled.anchorMethod);\n validatedHighlights.push({\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n\n return validatedHighlights;\n }\n\n /**\n * Validate and reconcile structured assessment elements.\n *\n * @param parsed - Already-parsed elements from the structured surface\n * @param content - Original content to validate offsets against\n * @returns Array of validated assessment matches\n */\n static parseAssessments(parsed: unknown[], content: string): AssessmentMatch[] {\n\n const assessments = parsed.filter((a): a is { exact: string; prefix?: string; suffix?: string; assessment: string } =>\n isObject(a) && isString(a.exact) && isString(a.assessment)\n );\n\n const validatedAssessments: AssessmentMatch[] = [];\n for (const assessment of assessments) {\n const reconciled = reconcileSelector(content, {\n exact: assessment.exact,\n ...(typeof assessment.prefix === 'string' ? { prefix: assessment.prefix } : {}),\n ...(typeof assessment.suffix === 'string' ? { suffix: assessment.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated assessment \"${assessment.exact}\"`);\n continue;\n }\n logAnchorMethod('assessment', assessment.exact, reconciled.anchorMethod);\n validatedAssessments.push({\n assessment: assessment.assessment,\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n\n return validatedAssessments;\n }\n\n /**\n * Validate structured tag elements into raw, pre-reconciliation tag inputs.\n * Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`\n * by anchoring `exact` against the source content.\n *\n * @param parsed - Already-parsed elements from the structured surface\n */\n static parseTags(parsed: unknown[]): RawTagInput[] {\n\n const valid = parsed.filter((t): t is RawTagInput =>\n isObject(t) && isString(t.exact) && t.exact.trim().length > 0\n );\n\n console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);\n\n return valid;\n }\n\n /**\n * Anchor raw tag inputs against source content and add category.\n */\n static validateTagOffsets(\n tags: RawTagInput[],\n content: string,\n category: string\n ): TagMatch[] {\n const validatedTags: TagMatch[] = [];\n for (const tag of tags) {\n const reconciled = reconcileSelector(content, {\n exact: tag.exact,\n ...(typeof tag.prefix === 'string' ? { prefix: tag.prefix } : {}),\n ...(typeof tag.suffix === 'string' ? { suffix: tag.suffix } : {}),\n });\n if (!reconciled) {\n console.warn(`[MotivationParsers] Dropped hallucinated tag \"${tag.exact}\" for category \"${category}\"`);\n continue;\n }\n logAnchorMethod('tag', tag.exact, reconciled.anchorMethod);\n validatedTags.push({\n category,\n exact: reconciled.exact,\n start: reconciled.start,\n end: reconciled.end,\n ...(reconciled.prefix !== undefined ? { prefix: reconciled.prefix } : {}),\n ...(reconciled.suffix !== undefined ? { suffix: reconciled.suffix } : {}),\n });\n }\n return validatedTags;\n }\n}\n\n/** Raw LLM-emitted tag, pre-reconciliation. */\nexport interface RawTagInput {\n exact: string;\n prefix?: string;\n suffix?: string;\n}\n\n/**\n * Audit one anchor-method classification. Forwards to the single decider\n * shared with the reference path (`anchor-audit.ts`) — which methods count as\n * risky is one judgement, and this file used to hold a second copy of it.\n */\nfunction logAnchorMethod(motivation: string, exact: string, anchorMethod: AnchorMethod): void {\n noteAnchor(motivation, exact, anchorMethod);\n}\n","/**\n * Annotation Detection\n *\n * Orchestrates the full annotation detection pipeline:\n * 1. Build AI prompts using MotivationPrompts\n * 2. Call AI inference\n * 3. Parse and validate results using MotivationParsers\n *\n * All methods take content as a string parameter — the worker process\n * fetches it and hands it in.\n */\n\nimport type { ElementSchema, InferenceClient } from '@semiont/inference';\nimport { chunkText, estimateTokens } from '@semiont/core';\nimport { boundedGenerateStructured } from './inference-call';\nimport { assertNotTruncated, callChunkSubdividing, deriveDetectionBudget, DETECTION_TEMPERATURE } from './detection/detection-chunking';\nimport { MotivationPrompts } from './detection/motivation-prompts';\nimport {\n MotivationParsers,\n COMMENT_ELEMENT_SCHEMA,\n HIGHLIGHT_ELEMENT_SCHEMA,\n ASSESSMENT_ELEMENT_SCHEMA,\n TAG_ELEMENT_SCHEMA,\n type CommentMatch,\n type HighlightMatch,\n type AssessmentMatch,\n type TagMatch,\n} from './detection/motivation-parsers';\nimport type { TagSchema } from '@semiont/core';\n\n/**\n * Per-chunk detection loop shared by the four motivations.\n *\n * Budgets derive from the provider's actual limits plus the measured prompt\n * scaffold (`buildPrompt('')`) — no literals. The prompt receives one chunk;\n * `parse` reconciles against the FULL document (the callers close over it),\n * so offsets index into the whole resource with no re-anchoring arithmetic.\n * Overlap duplicates pass through — the processor's span-keyed\n * `dedupeAnnotations` is the single dedupe point.\n *\n * `onActivity` fires whenever the detection is demonstrably alive: at each\n * chunk boundary (the count advances) AND periodically while one inference\n * call is in flight (the count repeats — liveness, not progress). Progress is\n * the worker's liveness heartbeat AND the client's inter-emission timeout\n * signal, so a silent single-chunk run kills a healthy job\n * (DETECTION-HEARTBEAT).\n */\nasync function detectInChunks<T>(\n client: InferenceClient,\n content: string,\n buildPrompt: (chunk: string) => string,\n motivation: string,\n elementSchema: ElementSchema,\n parse: (items: unknown[]) => T[],\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n): Promise<T[]> {\n const limits = await client.limits();\n const scaffoldTokens = estimateTokens(buildPrompt(''));\n // One motivation's spans per call — the single span family this prompt\n // asks for, the motivation-path analogue of one entity type.\n const { chunking, outputBudget } = deriveDetectionBudget(limits, scaffoldTokens, 1);\n const chunks = chunkText(content, chunking);\n\n const collected: T[] = [];\n for (let i = 0; i < chunks.length; i++) {\n // Structured surface: parsed elements or a throw — an unreadable model\n // response fails the job rather than reading as an empty detection. A\n // size-shaped failure (duration bound, truncation) subdivides in place\n // and retries smaller before it is allowed to fail the job.\n const items = await callChunkSubdividing<unknown>(motivation, chunks[i]!, chunking, async (piece) => {\n const response = await boundedGenerateStructured<unknown>(\n client, buildPrompt(piece), outputBudget, DETECTION_TEMPERATURE, elementSchema,\n // Still alive, same position (a long single call is otherwise silent).\n () => onActivity?.(i, chunks.length),\n );\n assertNotTruncated(response, `${motivation} detection`, i + 1, chunks.length, outputBudget);\n // Usage rides back so the telemetry record carries what the call COST\n // beside what it yielded — the provider's own counts, not an estimate.\n return { items: response.items, ...(response.usage ? { usage: response.usage } : {}) };\n });\n collected.push(...parse(items));\n if (i < chunks.length - 1) {\n onActivity?.(i + 1, chunks.length);\n }\n }\n return collected;\n}\n\nexport class AnnotationDetection {\n\n /**\n * Detect comments in content.\n *\n * `language` is the locale the LLM should write comment text in (annotation\n * body locale). `sourceLanguage` is the locale of the content being analyzed\n * (source-resource locale). See `types.ts` \"Locale conventions\" for the\n * full discussion.\n */\n static async detectComments(\n content: string,\n client: InferenceClient,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<CommentMatch[]> {\n return detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildCommentPrompt(chunk, instructions, tone, density, language, sourceLanguage),\n 'comment', COMMENT_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseComments(items, content),\n onActivity,\n );\n }\n\n /**\n * Detect highlights in content.\n *\n * Highlights have no body — only `sourceLanguage` (source-resource locale)\n * applies, used in the prompt so the LLM analyzes non-English source\n * correctly.\n */\n static async detectHighlights(\n content: string,\n client: InferenceClient,\n instructions?: string,\n density?: number,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<HighlightMatch[]> {\n return detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildHighlightPrompt(chunk, instructions, density, sourceLanguage),\n 'highlight', HIGHLIGHT_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseHighlights(items, content),\n onActivity,\n );\n }\n\n /**\n * Detect assessments in content.\n *\n * `language` is the locale the LLM should write assessment text in\n * (annotation body locale). `sourceLanguage` is the locale of the content\n * being analyzed (source-resource locale).\n */\n static async detectAssessments(\n content: string,\n client: InferenceClient,\n instructions?: string,\n tone?: string,\n density?: number,\n language?: string,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<AssessmentMatch[]> {\n return detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildAssessmentPrompt(chunk, instructions, tone, density, language, sourceLanguage),\n 'assessment', ASSESSMENT_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseAssessments(items, content),\n onActivity,\n );\n }\n\n /**\n * Detect tags in content for a specific category.\n *\n * The full `TagSchema` is supplied by the dispatcher (resolved against\n * the per-KB tag-schema projection at job-creation time) so the worker\n * is independent of the registry.\n *\n * `sourceLanguage` is the locale of the content being analyzed. Body-locale\n * (`language`) doesn't influence the tag prompt — categories are schema\n * identifiers, not LLM-generated text — so it's consumed at the body-stamp\n * site, not here.\n */\n static async detectTags(\n content: string,\n client: InferenceClient,\n schema: TagSchema,\n category: string,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n ): Promise<TagMatch[]> {\n const categoryInfo = schema.tags.find((t) => t.name === category);\n if (!categoryInfo) {\n throw new Error(`Invalid category \"${category}\" for schema ${schema.id}`);\n }\n\n // Parse per chunk; anchor once against the full document afterward.\n const parsedTags = await detectInChunks(\n client, content,\n (chunk) => MotivationPrompts.buildTagPrompt(\n chunk,\n category,\n schema.name,\n schema.description,\n schema.domain,\n categoryInfo.description,\n categoryInfo.examples,\n sourceLanguage\n ),\n 'tag', TAG_ELEMENT_SCHEMA,\n (items) => MotivationParsers.parseTags(items),\n onActivity,\n );\n return MotivationParsers.validateTagOffsets(parsedTags, content, category);\n }\n}\n","import type { ElementSchema, InferenceClient } from '@semiont/inference';\nimport { chunkText, estimateTokens, getLocaleEnglishName, isObject, isString, type Logger } from '@semiont/core';\nimport { boundedGenerateStructured, boundedGenerateWithMetadata } from '../inference-call';\nimport { assertNotTruncated, callChunkSubdividing, deriveDetectionBudget, DETECTION_TEMPERATURE, YIELD_COLLAPSE_BAND, YieldCollapseError } from './detection-chunking';\n\n/**\n * Entity reference extracted from text — pre-reconciliation.\n *\n * The LLM emits `exact` (verbatim text span), `entityType`, and optional\n * `prefix` / `suffix` context for disambiguation. Offsets are not asked\n * for — `reconcileSelector` computes them by anchoring `exact` against\n * the source content in the calling processor.\n */\nexport interface ExtractedEntity {\n exact: string;\n entityType: string;\n prefix?: string;\n suffix?: string;\n}\n\n/**\n * JSON Schema for one extracted entity — the provider-enforced shape.\n *\n * Declared adjacent to `ExtractedEntity` deliberately: the schema is what\n * constrains the wire and the interface is what the code consumes, and\n * nothing verifies they agree — adjacency is the drift guard. The\n * per-element `isObject`/`isString` checks below stay as the structural\n * backstop (STRUCTURED-INFERENCE D5).\n *\n * `prefix`/`suffix` are deliberately NOT in `required`: with all four\n * required, models return `\"prefix\": \"\"` instead of omitting the key\n * (measured 2026-08-06), turning \"sometimes absent\" into \"always present,\n * sometimes empty\" — an anchoring-path change Phase 3 re-examines before\n * anyone relies on it.\n */\nconst ENTITY_ELEMENT_SCHEMA: ElementSchema = {\n type: 'object',\n properties: {\n exact: { type: 'string' },\n entityType: { type: 'string' },\n prefix: { type: 'string' },\n suffix: { type: 'string' },\n },\n required: ['exact', 'entityType'],\n additionalProperties: false,\n};\n\n/**\n * Extract entity references from text using AI.\n *\n * Locale: entity references' bodies are entity-type identifiers (not\n * LLM-generated natural-language text), so only `sourceLanguage` (source-\n * resource locale) is meaningful here — it's used in the prompt so the LLM\n * analyzes non-English source correctly. There's no body-locale parameter.\n *\n * @param text - The text to analyze\n * @param entityTypes - Array of entity types to detect (optionally with examples)\n * @param client - Inference client for AI operations\n * @param includeDescriptiveReferences - Include anaphoric/cataphoric references (default: false)\n * @param logger - Logger for entity-extraction diagnostics (parse failures,\n * anchor decisions, drops). Required so dropped/filtered entities never\n * disappear silently.\n * @param sourceLanguage - BCP-47 tag for the source content's language\n * @param onActivity - Invoked with (completedChunks, totalChunks) whenever\n * the extraction is demonstrably alive: at each chunk boundary (the count\n * advances) AND periodically while a single inference call is in flight\n * (the count repeats — liveness, not progress). The caller MUST forward\n * this to its progress channel: progress is the worker's liveness\n * heartbeat for the stall watchdog, and the client's timeout is an\n * INTER-EMISSION one, so a silent single-chunk run kills a healthy job\n * (DETECTION-HEARTBEAT).\n * @returns Array of extracted entities with their character offsets\n */\n/** Output cap for the count call: the answer is one number (≤7 digits), and a\n * tiny cap is itself the safety — a ~5-token output structurally cannot loop\n * or truncate the way the extraction's array can. */\nconst COUNT_MAX_TOKENS = 16;\n\n/** The count answer, read as its leading integer. \"Respond with only the\n * number\" is the prompt's contract, but a model that pads it (\"There are 50\")\n * still yields its verdict; anything with no integer yields none. */\nfunction parseCount(text: string): number | undefined {\n const m = text.trim().match(/\\d+/);\n return m ? Number(m[0]) : undefined;\n}\n\n/**\n * The count-verifier (OLLAMA-DETECTION-TESTING P3c): guard a SUCCESSFUL\n * extraction against silent yield collapse (F7) — a schema-clean response\n * carrying a fraction of the mentions present, measured deterministic and\n * classification-invisible, so nothing else can catch it.\n *\n * A cheap count call (\"respond with only the number\") sets the expectation\n * FROM THE SAME TEXT — corpus-free, per the no-input-assumptions principle.\n * An extraction under 1/BAND of the count throws the collapse verdict, which\n * subdivision treats like truncation (descend by size) except at the floor,\n * where it fails the job loudly (user-ratified).\n *\n * The verifier's own failure — count call errors, or answers with no number —\n * disables it for the chunk (warn, pass through): a safety net's outage must\n * not take down a healthy extraction. Known limit, documented in the bug\n * report: the count saturates past ~8K chars, so margins are cleanest at\n * post-descent sizes; it still flagged every measured collapse at 16K/32K.\n */\nasync function assertYieldNotCollapsed(\n client: InferenceClient,\n piece: string,\n items: readonly unknown[],\n entityTypesDescription: string,\n logger: Logger,\n): Promise<void> {\n const prompt = `Count every mention of: ${entityTypesDescription} in the following text. Repeated mentions of the same entity count separately. Respond with only the number.\n\nText:\n\"\"\"\n${piece}\n\"\"\"`;\n\n let counted: number | undefined;\n try {\n const response = await boundedGenerateWithMetadata(client, prompt, COUNT_MAX_TOKENS, DETECTION_TEMPERATURE, undefined, logger);\n counted = parseCount(response.text);\n } catch (err) {\n logger.warn('Count-verifier call failed — yield check skipped for this chunk', {\n pieceChars: piece.length,\n error: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n if (counted === undefined) {\n logger.warn('Count-verifier answer carried no number — yield check skipped for this chunk', { pieceChars: piece.length });\n return;\n }\n if (items.length * YIELD_COLLAPSE_BAND < counted) {\n // The salvage rides the error: descent discards it (a smaller re-run does\n // better), the floor accepts it (better than nothing, and every span is\n // write-time-verified).\n throw new YieldCollapseError(\n `Extraction found ${items.length} entities where a count call reports ~${counted} mentions (band ×${YIELD_COLLAPSE_BAND}) on a ${piece.length}-char chunk — silent yield collapse (F7): deterministic — a same-size retry returns the identical under-report.`,\n [...items],\n );\n }\n}\n\nexport async function extractEntities(\n exact: string,\n entityTypes: string[] | { type: string; examples?: string[] }[],\n client: InferenceClient,\n includeDescriptiveReferences: boolean,\n logger: Logger,\n sourceLanguage?: string,\n onActivity?: (completedChunks: number, totalChunks: number) => void,\n): Promise<ExtractedEntity[]> {\n\n // Format entity types for the prompt\n const entityTypesDescription = entityTypes.map(et => {\n if (typeof et === 'string') {\n return et;\n }\n return et.examples && et.examples.length > 0\n ? `${et.type} (examples: ${et.examples.slice(0, 3).join(', ')})`\n : et.type;\n }).join(', ');\n\n // Build prompt with optional support for anaphoric/cataphoric references\n // Anaphora: references that point backward (e.g., \"John arrived. He was tired.\")\n // Cataphora: references that point forward (e.g., \"When she arrived, Mary was surprised.\")\n // When enabled, include substantive descriptive references beyond simple pronouns\n const descriptiveReferenceGuidance = includeDescriptiveReferences\n ? `\nInclude both:\n- Direct mentions (names, proper nouns)\n- Descriptive references (substantive phrases that refer to entities)\n\nFor descriptive references, include:\n- Definite descriptions: \"the Nobel laureate\", \"the tech giant\", \"the former president\"\n- Role-based references: \"the CEO\", \"the physicist\", \"the author\", \"the owner\", \"the contractor\"\n- Epithets with context: \"the Cupertino-based company\", \"the iPhone maker\"\n- References to entities even when identity is unknown or unspecified\n\nDo NOT include:\n- Simple pronouns alone: he, she, it, they, him, her, them\n- Generic determiners alone: this, that, these, those\n- Possessives without substance: his, her, their, its\n\nExamples:\n- For \"Marie Curie\", include \"the Nobel laureate\" and \"the physicist\" but NOT \"she\"\n- For an unknown person, include \"the owner\" or \"the contractor\" (role-based references count even when identity is unspecified)\n`\n : `\nFind direct mentions only (names, proper nouns). Do not include pronouns or descriptive references.\n`;\n\n const sourceLangGuidance = sourceLanguage\n ? `\\nSource text language: ${getLocaleEnglishName(sourceLanguage) || sourceLanguage}.\\n`\n : '';\n\n // The LLM is asked for `exact`, `prefix`, and `suffix` — no character\n // offsets. Offsets get computed by `reconcileSelector` against the\n // source content. Asking the model for offsets wastes tokens and\n // encourages it to fabricate where it shouldn't.\n const buildPrompt = (text: string): string => `Identify entity references in the following text. Look for mentions of: ${entityTypesDescription}.\n${descriptiveReferenceGuidance}${sourceLangGuidance}\nText to analyze:\n\"\"\"\n${text}\n\"\"\"\n\nRespond with a JSON array of entities found. Each entity should have:\n- exact: the exact text span from the input (quoted verbatim — character-for-character)\n- entityType: one of the provided entity types\n- prefix: up to 64 characters of text immediately before the entity (used to disambiguate when the same text appears more than once)\n- suffix: up to 64 characters of text immediately after the entity (same purpose)\n\nIf no entities are found, respond with an empty array [].\n\nExample output:\n[{\"exact\":\"Alice\",\"entityType\":\"Person\",\"prefix\":\"\",\"suffix\":\" went to\"},{\"exact\":\"Paris\",\"entityType\":\"Location\",\"prefix\":\"went to \",\"suffix\":\" yesterday\"}]`;\n\n // Budgets derive from the provider's actual limits + the measured scaffold\n // (the template around the content) — no literals. Input is chunked only\n // when the derived budget forces it; small documents make one call, as\n // before.\n const limits = await client.limits();\n // The provider DECLARES whether its extractions get count-verified\n // (universal for real providers since the 2026-09-05 ruling — unverified\n // completeness is not a savings). Jobs does no provider-specific switching:\n // whatever varies by provider is a capability on the InferenceClient\n // contract, read here like `maxConcurrency`.\n const verifyYield = client.verifyDetectionYield;\n const scaffoldTokens = estimateTokens(buildPrompt(''));\n // One call asks for every type in `entityTypes` — the processor's per-type\n // loop passes one, so this is 1 in production today.\n const { chunking, outputBudget } = deriveDetectionBudget(limits, scaffoldTokens, entityTypes.length);\n const chunks = chunkText(exact, chunking);\n\n logger.debug('Sending entity extraction request', {\n entityTypes: entityTypesDescription,\n chunks: chunks.length,\n chunkSizeTokens: chunking.chunkSize,\n outputBudget,\n });\n\n const collected: ExtractedEntity[] = [];\n for (let i = 0; i < chunks.length; i++) {\n // The structured surface returns parsed elements or THROWS — an\n // unreadable model response is a job failure, never a silent []. A\n // size-shaped failure (duration bound, truncation) subdivides in place\n // and retries smaller before it is allowed to fail the job.\n const items = await callChunkSubdividing<unknown>('reference', chunks[i]!, chunking, async (piece) => {\n const response = await boundedGenerateStructured<unknown>(\n client,\n buildPrompt(piece),\n outputBudget,\n DETECTION_TEMPERATURE,\n ENTITY_ELEMENT_SCHEMA,\n // Still alive, same position: a long single call would otherwise emit\n // nothing at all between start and finish.\n () => onActivity?.(i, chunks.length),\n logger,\n );\n logger.debug('Got entity extraction response', {\n chunk: i + 1,\n chunks: chunks.length,\n pieceChars: piece.length,\n items: response.items.length,\n });\n\n // Truncation is data loss, not \"no entities\" — check it BEFORE\n // consuming: a truncated structured response can still carry a valid\n // partial array, so the items themselves cannot signal the loss.\n assertNotTruncated(response, 'Entity extraction', i + 1, chunks.length, outputBudget);\n // And a CLEAN response can still be a silent under-report (F7) — the\n // count-verifier is the only signal for that, and a flag throws the\n // collapse verdict so subdivision changes the input.\n if (verifyYield) {\n await assertYieldNotCollapsed(client, piece, response.items, entityTypesDescription, logger);\n }\n // Usage rides back so the telemetry record carries what the call COST\n // beside what it yielded — the provider's own counts, not an estimate.\n return { items: response.items, ...(response.usage ? { usage: response.usage } : {}) };\n }, logger);\n\n for (const e of items) {\n // No dedupe here: overlap duplicates from adjacent chunks pass through\n // to the processor's span-keyed dedupeAnnotations — the single dedupe\n // point.\n if (isObject(e) && isString(e.exact) && isString(e.entityType)) {\n collected.push({\n exact: e.exact,\n entityType: e.entityType,\n ...(isString(e.prefix) ? { prefix: e.prefix } : {}),\n ...(isString(e.suffix) ? { suffix: e.suffix } : {}),\n });\n } else {\n logger.debug('Dropped malformed LLM entity', { entity: e });\n }\n }\n\n // Chunk boundary: the count advances (real progress).\n if (i < chunks.length - 1) {\n onActivity?.(i + 1, chunks.length);\n }\n }\n\n return collected;\n}\n","/**\n * Resource Generation\n *\n * Generates markdown resources from topics using AI inference.\n */\n\nimport { getLocaleEnglishName, deriveViews } from '@semiont/core';\nimport type { GatheredContext, Logger, SupportedMediaType } from '@semiont/core';\nimport type { InferenceClient } from '@semiont/inference';\nimport { boundedGenerateWithMetadata } from '../inference-call';\n\n\nfunction getLanguageName(locale: string): string {\n return getLocaleEnglishName(locale) || locale;\n}\n\n// Prompt-embedding caps — bound the context fed to the model (CONTEXT-IDENTIFIERS\n// D4: named constants; promote to caller-tunable options only if a consumer\n// actually hits the wall). A fact past these bounds never reaches the model.\nconst RESOURCE_CONTENT_CAP = 4000;\nconst SEMANTIC_MATCH_LIMIT = 3;\nconst SEMANTIC_MATCH_CHARS = 240;\n\n/**\n * Model-visible identifier handle for an embedded excerpt — the ONE bracket\n * convention across all context sections (CONTEXT-IDENTIFIERS D1/D2): resource id\n * always, annotation id as a suffix when the excerpt is annotation-derived.\n */\nfunction idLabel(resourceId: string, annotationId?: string): string {\n return `[${resourceId}${annotationId ? `/${annotationId}` : ''}]`;\n}\n\n/**\n * The canonical gateway default for maxTokens — a short-definition size. The\n * UI also initialises its field to 500 as a UX convenience, but the\n * authoritative fallback lives here so direct API callers get a sensible\n * limit when they omit the parameter. Exported so error messages elsewhere\n * can name the ceiling that actually applied (one constant, no drift).\n */\nexport const DEFAULT_MAX_TOKENS = 500;\n\n/**\n * Generate resource content using inference.\n *\n * Locale parameters: `locale` is the *body* locale — the language the\n * generated resource should be written in (sourced from the user's UI\n * locale). `sourceLanguage` is the *source* locale — the language of the\n * referenced resource whose context (selected passage, surrounding text)\n * is embedded into the prompt. They're independent: a German user can\n * generate German content from an English source resource. See\n * `types.ts` \"Locale conventions\" for the full discussion.\n */\nexport async function generateResourceFromTopic(\n topic: string,\n entityTypes: string[],\n client: InferenceClient,\n logger: Logger,\n userPrompt?: string,\n locale?: string,\n context?: GatheredContext,\n temperature?: number,\n maxTokens?: number,\n sourceLanguage?: string,\n outputMediaType: SupportedMediaType = 'text/markdown',\n task: string = 'resource',\n structure?: string,\n cite: boolean = false,\n repair?: { source: string; error: string }\n): Promise<{ title: string; content: string; truncated: boolean }> {\n logger.debug('Generating resource from topic', {\n topicPreview: topic.substring(0, 100),\n entityTypes,\n hasUserPrompt: !!userPrompt,\n locale,\n sourceLanguage,\n hasContext: !!context,\n temperature,\n maxTokens,\n outputMediaType,\n task,\n structure\n });\n\n // Use provided values or defaults.\n const finalTemperature = temperature ?? 0.7;\n const finalMaxTokens = maxTokens ?? DEFAULT_MAX_TOKENS;\n\n // Determine language instructions. Body locale (\"write the resource in X\")\n // and source locale (\"the embedded source text is in Y\") are independent —\n // a German user can ask for a German write-up of an English source.\n const languageInstruction = locale && locale !== 'en'\n ? `\\n\\nIMPORTANT: Write the entire resource in ${getLanguageName(locale)}.`\n : '';\n const sourceLanguageInstruction = sourceLanguage\n ? `\\n\\nThe source resource and embedded context are in ${getLanguageName(sourceLanguage)}.`\n : '';\n\n // ── Context sections — switch on the unified GatheredContext focus ─────────\n // Annotation focus drives the annotation + selected-passage sections; resource\n // focus drives a minimal resource anchor (the full grounding — summary,\n // suggestedReferences, content — is YIELD-FROM-RESOURCE Gap B). The graph and\n // semantic sections are shared base, rendered for either focus.\n let annotationSection = '';\n let contextSection = '';\n let resourceSection = '';\n let graphSection = '';\n\n if (context) {\n const { focus } = context;\n // The focal resource's id — equal to the id `buildKnowledgeGraph` (P3) anchored\n // the graph's main node on, so `deriveViews` resolves edges. Read directly off\n // the descriptor (a plain string on the generated type); there is no event id to\n // thread here as the matcher had.\n const mainResourceId =\n focus.kind === 'annotation'\n ? focus.sourceResource['@id']\n : focus.resource['@id'];\n const focalAnnotationId = focus.kind === 'annotation' ? focus.annotation.id : undefined;\n\n if (focus.kind === 'annotation') {\n const parts: string[] = [];\n parts.push(`- Annotation motivation: ${focus.annotation.motivation}`);\n parts.push(`- Source resource: ${focus.sourceResource.name} ${idLabel(mainResourceId)}`);\n // Include body text for commenting/assessing annotations\n const { motivation, body } = focus.annotation;\n if (motivation === 'commenting' || motivation === 'assessing') {\n const bodyItem = Array.isArray(body) ? body[0] : body;\n if (bodyItem && 'value' in bodyItem && bodyItem.value) {\n const label = motivation === 'commenting' ? 'Comment' : 'Assessment';\n parts.push(`- ${label}: ${bodyItem.value}`);\n }\n }\n // The wizard's Hint: supplements or replaces the selected text as the\n // subject signal (GatheredContext.json focus.userHint). The matcher folds\n // it into its search term the same way — the two consumers of one field.\n if (focus.userHint) {\n parts.push(`- User hint (steers what to generate): ${focus.userHint}`);\n }\n annotationSection = `\\n\\nAnnotation context:\\n${parts.join('\\n')}`;\n\n if (focus.selected) {\n const { before, text, after } = focus.selected;\n contextSection = `\\n\\nSource document context:\n---\n${before ? `...${before}` : ''}\n**[${text}]**\n${after ? `${after}...` : ''}\n---\n`;\n }\n } else {\n // Resource focus — ground in the focal resource's summary, suggested\n // references, and content (omit-empty). Content is capped per resource to\n // bound prompt size; the caller already chose breadth via gather.resource.\n const parts = [`- Resource: ${focus.resource.name} ${idLabel(mainResourceId)}`];\n if (focus.summary) parts.push(`- Summary: ${focus.summary}`);\n if (focus.suggestedReferences && focus.suggestedReferences.length > 0) {\n parts.push(`- Suggested references: ${focus.suggestedReferences.join(', ')}`);\n }\n resourceSection = `\\n\\nResource context:\\n${parts.join('\\n')}`;\n\n if (focus.content?.main) {\n resourceSection += `\\n\\nResource content:\\n---\\n${focus.content.main.slice(0, RESOURCE_CONTENT_CAP)}\\n---`;\n }\n const related = Object.entries(focus.content?.related ?? {});\n if (related.length > 0) {\n const blocks = related\n .map(([id, text]) => `[${id}]\\n${text.slice(0, RESOURCE_CONTENT_CAP)}`)\n .join('\\n\\n');\n resourceSection += `\\n\\nRelated resource content:\\n---\\n${blocks}\\n---`;\n }\n }\n\n // Shared base: graph-derived neighborhood (connections / citedBy / siblings)\n // plus the LLM relationship summary, recomputed from the unified graph.\n if (mainResourceId) {\n const views = deriveViews(context.graph, mainResourceId, focalAnnotationId);\n const parts: string[] = [];\n\n if (views.connections.length > 0) {\n const connList = views.connections\n .map(c => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(', ')})` : ''} ${idLabel(c.resourceId)}`)\n .join(', ');\n parts.push(`- Connected resources: ${connList}`);\n }\n\n if (views.citedByCount > 0) {\n const citedNames = views.citedBy.map(c => `${c.resourceName} ${idLabel(c.resourceId)}`).join(', ');\n parts.push(`- This resource is cited by ${views.citedByCount} other resource${views.citedByCount > 1 ? 's' : ''}${citedNames ? `: ${citedNames}` : ''}`);\n }\n\n if (views.siblingEntityTypes.length > 0) {\n parts.push(`- Related entity types in this document: ${views.siblingEntityTypes.join(', ')}`);\n }\n\n if (context.inferredRelationshipSummary) {\n parts.push(`- Relationship summary: ${context.inferredRelationshipSummary}`);\n }\n\n if (parts.length > 0) {\n graphSection = `\\n\\nKnowledge graph context:\\n${parts.join('\\n')}`;\n }\n }\n }\n\n // Build semantic context section if available — the vector matches the gather\n // flow already retrieved for the focal passage, used to ground generation (RAG).\n // Capped and truncated to bound prompt cost (see the named caps above); the\n // gather step pre-filters to ≤10 matches above a 0.5 cosine threshold. Each\n // passage carries its source id so the model can attribute what it uses.\n // See .plans/SEMANTIC-CONTEXT-RAG.md + .plans/CONTEXT-IDENTIFIERS.md.\n let semanticContextSection = '';\n const similar = context?.semanticContext?.similar ?? [];\n if (similar.length > 0) {\n const lines = [...similar]\n .sort((a, b) => b.score - a.score)\n .slice(0, SEMANTIC_MATCH_LIMIT)\n .map(m => `- ${idLabel(m.resourceId, m.annotationId)} (${m.score.toFixed(2)})${m.machineRead ? ' [OCR]' : ''} ${m.text.slice(0, SEMANTIC_MATCH_CHARS)}`);\n // A passage marked [OCR] was recognized from a scanned image, so its\n // wording — and especially its digits — may be misread. Saying so is the\n // whole point of carrying the flag: the model is the only reader of these\n // passages, and it would otherwise quote a scanned figure as confidently\n // as a typed one.\n const ocrNote = similar.some(m => m.machineRead)\n ? '\\nPassages marked [OCR] were read from scanned images by character recognition; treat their exact wording and numbers as uncertain, and say so if you rely on one.'\n : '';\n semanticContextSection = `\\n\\nRelated passages from the knowledge base:\\n${lines.join('\\n')}${ocrNote}`;\n }\n\n // ── Task framing (YIELD-STRUCTURE D1): canonical tasks map to tested framings;\n // an unknown task string is used VERBATIM as the framing instruction (loud\n // degrade — warn, never a silent fallback to 'resource').\n let leadLine: string;\n if (task === 'resource') {\n leadLine = `Generate a concise, informative resource about \"${topic}\".`;\n } else if (task === 'answer') {\n leadLine = `Answer the following question directly and concisely, grounded in the provided context: \"${topic}\"`;\n } else if (task === 'summary') {\n leadLine = `Write a concise summary of \"${topic}\".`;\n } else {\n logger.warn('Unknown task — using it verbatim as the framing instruction', { task });\n leadLine = `${task}\\nTopic: \"${topic}\"`;\n }\n\n // ── Structure directive (YIELD-STRUCTURE D2/D4): emitted ONLY when the caller\n // sets one — unset means no directive at all (the task framing and the model\n // determine shape). Never derived from the token budget: maxTokens is length\n // only. The forced `# Title` heading exists only under canonical 'sections'.\n const isPlainText = outputMediaType === 'text/plain';\n // PDF output means the model authors Typst source (Q1, direct Typst); the\n // worker compiles it. The prompt therefore teaches Typst syntax, and the\n // markdown scaffolding never applies.\n const isPdf = outputMediaType === 'application/pdf';\n let structureRequirement = '';\n let titleRequirement = '';\n if (structure === 'sections') {\n structureRequirement = isPdf\n ? '\\n- Organize the content into titled sections (= Heading) with well-structured paragraphs'\n : isPlainText\n ? '\\n- Organize the content into titled sections with well-structured paragraphs'\n : '\\n- Organize the content into titled sections (## Section) with well-structured paragraphs';\n if (!isPlainText && !isPdf) {\n titleRequirement = '\\n- Start with a clear heading (# Title)';\n }\n } else if (structure === 'prose') {\n structureRequirement = '\\n- Write flowing, well-structured paragraphs with no section headings';\n } else if (structure === 'chat') {\n structureRequirement = '\\n- Structure the content as a conversational chat transcript — a sequence of alternating, speaker-labeled turns (no section headings)';\n } else if (structure) {\n logger.warn('Unknown structure — passing it through as freeform organization guidance', { structure });\n structureRequirement = `\\n- Organize the output as: ${structure}`;\n }\n\n // Citation instruction (INLINE-CITATIONS): ask the model to emit [[<id>]]\n // transport tokens next to each claim, citing only ids the context embedding\n // shows. The worker strips the tokens and reconciles them into linking\n // annotations — they never reach the stored content.\n const citeRequirement = cite\n ? '\\n- Ground every claim in the provided context. Immediately after each claim, cite its source by emitting [[<id>]], where <id> is an id shown in square brackets in the context above (for a passage labeled [abc], emit [[abc]]). Cite only ids that appear in the context.'\n : '';\n\n const formatRequirements = isPdf\n ? `- Write the response as Typst markup (the Typst typesetting language — not markdown, not LaTeX)\n- Headings are written as = Heading (deeper levels == Subheading); everything else is plain prose paragraphs\n- Do not emit markdown syntax or code fences`\n : isPlainText\n ? `- Write the response as plain text — no formatting markup (no #, *, backticks, headings, or links)\n- Begin with the title on its own first line`\n : `- Use markdown formatting\n- Write the response as markdown`;\n\n // Compile-repair context (PDF-GENERATION P3): the previous Typst source\n // failed to compile; the legible compiler diagnostics go back to the model\n // as an authoritative instruction to fix and re-emit the full document.\n const repairSection = repair\n ? `\\n\\nYour previous attempt failed to compile. Fix the error and return the complete corrected document — full source, not a diff.\nCompile error:\n${repair.error}\nPrevious source:\n${repair.source}`\n : '';\n\n // The caller's prompt is an authoritative leading Instruction (YIELD-STRUCTURE D3),\n // not background \"additional context\" — task = what to produce, prompt = how.\n const prompt = `${leadLine}\n${userPrompt ? `Instruction: ${userPrompt}` : ''}${repairSection}\n${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(', ')}.` : ''}${annotationSection}${contextSection}${resourceSection}${graphSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}\n\nRequirements:\n- Aim for approximately ${finalMaxTokens} tokens of content\n- Be factual and informative${structureRequirement}${titleRequirement}${citeRequirement}\n${formatRequirements}`;\n\n // Simple parser - just use the response directly as markdown\n const parseResponse = (response: string): { title: string; content: string } => {\n // Clean up any markdown code fences if present\n let content = response.trim();\n if (content.startsWith('```markdown') || content.startsWith('```md') || content.startsWith('```typst')) {\n content = content.slice(content.indexOf('\\n') + 1);\n const endIndex = content.lastIndexOf('```');\n if (endIndex !== -1) {\n content = content.slice(0, endIndex);\n }\n } else if (content.startsWith('```')) {\n content = content.slice(3);\n const endIndex = content.lastIndexOf('```');\n if (endIndex !== -1) {\n content = content.slice(0, endIndex);\n }\n }\n\n content = content.trim();\n\n // Title is provided by the caller (topic), not extracted from generated content\n return {\n title: topic,\n content: content\n };\n };\n\n logger.debug('Sending prompt to inference', {\n promptLength: prompt.length,\n temperature: finalTemperature,\n maxTokens: finalMaxTokens\n });\n const response = await boundedGenerateWithMetadata(client, prompt, finalMaxTokens, finalTemperature, undefined, logger);\n logger.debug('Got response from inference', { responseLength: response.text.length, stopReason: response.stopReason });\n\n const result = parseResponse(response.text);\n logger.debug('Parsed response', {\n hasTitle: !!result.title,\n titleLength: result.title?.length,\n hasContent: !!result.content,\n contentLength: result.content?.length\n });\n\n // The provider's stopReason collapses to one protocol-owned bit HERE and\n // nowhere else (GENERATE-FROM-RESOURCE D6): 'max_tokens' means the artifact\n // is cut off, not complete; every other reason is a natural stop.\n return { ...result, truncated: response.stopReason === 'max_tokens' };\n}\n","/**\n * Typst compiler invocation (PDF-GENERATION P3).\n *\n * The model writes Typst source (Q1, settled 2026-08-04: direct Typst); this\n * module turns it into PDF bytes via the pinned binary the worker image ships\n * (P2 — `/usr/local/bin/typst`, v0.15.1, resolved through PATH so tests can\n * substitute a fake). Compile failures return the legible error text — Typst's\n * `file:line:col` + caret diagnostics are the load-bearing input to the bounded\n * repair loop in `processGenerationJob`.\n */\nimport { execFileSync } from 'node:child_process';\nimport { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Every compile pins the PDF creation timestamp. Unpinned compiles of\n * identical source produce different bytes every time (CreationDate/ModDate),\n * which churns the content checksum and makes the Smelter re-embed a document\n * that did not change — the spurious-re-embed failure S11/S12 exist to\n * prevent. The value is arbitrary; its fixity is the point.\n */\nexport const PINNED_CREATION_TIMESTAMP = 1700000000;\n\n/**\n * Model-authored source can fail to compile; the error is fed back for repair\n * a bounded number of times, then the job fails loudly with the compiler's\n * diagnostics. Bounded because generation is non-idempotent and paid per\n * attempt — this is a repair loop, not a retry loop.\n */\nexport const MAX_COMPILE_REPAIRS = 2;\n\nexport function compileTypst(source: string): { pdf: Uint8Array } | { error: string } {\n const dir = mkdtempSync(join(tmpdir(), 'typst-'));\n try {\n const inFile = join(dir, 'doc.typ');\n const outFile = join(dir, 'doc.pdf');\n writeFileSync(inFile, source);\n try {\n execFileSync(\n 'typst',\n ['compile', '--creation-timestamp', String(PINNED_CREATION_TIMESTAMP), inFile, outFile],\n { stdio: ['ignore', 'pipe', 'pipe'] },\n );\n } catch (err) {\n const stderr = (err as { stderr?: Buffer }).stderr;\n return { error: stderr?.length ? stderr.toString('utf8') : String(err) };\n }\n return { pdf: new Uint8Array(readFileSync(outFile)) };\n } finally {\n rmSync(dir, { recursive: true, force: true });\n }\n}\n","/**\n * Citation-token resolver (INLINE-CITATIONS P1).\n *\n * Under `cite`, the generation prompt asks the model to emit `[[<resourceId>]]` /\n * `[[<resourceId>/<annotationId>]]` transport tokens after each claim, citing ids\n * the context embedding put in front of it (CONTEXT-IDENTIFIERS). Tokens are\n * TRANSPORT, not content: this resolver parses them, validates each id against the\n * ids actually present in the embedded context (the hallucination guard — an\n * unknown id is dropped loudly, never silently linked), STRIPS them from the\n * content before upload, and returns claim-span citations the worker mints as W3C\n * linking annotations on the derived resource. See .plans/INLINE-CITATIONS.md.\n */\nimport type { GatheredContext, Logger } from '@semiont/core';\n\nexport interface GenerationCitation {\n /** The cited source resource (validated present in the embedded context). */\n resourceId: string;\n /** The contributing annotation, when the cited excerpt was annotation-derived. */\n annotationId?: string;\n /** Claim span in the FINAL (token-stripped) content; substring(start, end) === exact. */\n start: number;\n end: number;\n exact: string;\n}\n\n/** `[[rid]]` or `[[rid/annId]]` — ids are bare (no whitespace, brackets, or slashes). */\nconst CITATION_TOKEN = /\\[\\[([^\\s[\\]/]+)(?:\\/([^\\s[\\]/]+))?\\]\\]/g;\n\n/**\n * The ids the context embedding actually put in front of the model — the\n * hallucination guard's ground truth. Mirrors what the prompt renderers label:\n * the focal resource, graph resource nodes, semantic matches, related content.\n */\nexport function collectContextResourceIds(context: GatheredContext | undefined): Set<string> {\n const ids = new Set<string>();\n if (!context) return ids;\n\n const { focus } = context;\n ids.add(focus.kind === 'annotation' ? focus.sourceResource['@id'] : focus.resource['@id']);\n for (const node of context.graph.nodes) {\n if (node.type === 'resource') ids.add(node.id);\n }\n for (const m of context.semanticContext?.similar ?? []) ids.add(m.resourceId);\n if (focus.kind === 'resource') {\n for (const id of Object.keys(focus.content?.related ?? {})) ids.add(id);\n }\n return ids;\n}\n\nexport function resolveCitationTokens(\n content: string,\n validResourceIds: ReadonlySet<string>,\n logger: Logger,\n): { content: string; citations: GenerationCitation[] } {\n const citations: GenerationCitation[] = [];\n let clean = '';\n let last = 0;\n\n for (const match of content.matchAll(CITATION_TOKEN)) {\n const token = match[0];\n const citedResourceId = match[1]!;\n const annotationId = match[2];\n\n // Append the text before the token, dropping the whitespace run immediately\n // preceding it so stripping never leaves a dangling space.\n clean += content.slice(last, match.index).replace(/[ \\t]+$/, '');\n last = match.index! + token.length;\n\n if (!validResourceIds.has(citedResourceId)) {\n logger.warn('Citation token references an id absent from the provided context — dropped', {\n resourceId: citedResourceId,\n });\n continue;\n }\n\n // Claim span: the sentence preceding the token, computed against the clean\n // content. Offsets are final — later appends never shift earlier positions.\n const end = clean.length;\n let start = 0;\n for (let i = end - 2; i >= 0; i--) {\n const ch = clean[i];\n if (ch === '.' || ch === '!' || ch === '?' || ch === '\\n') {\n start = i + 1;\n break;\n }\n }\n while (start < end && /\\s/.test(clean[start]!)) start++;\n const exact = clean.slice(start, end);\n if (exact.length === 0) {\n logger.warn('Citation token has no preceding claim text — dropped', {\n resourceId: citedResourceId,\n });\n continue;\n }\n\n citations.push({\n resourceId: citedResourceId,\n ...(annotationId ? { annotationId } : {}),\n start,\n end,\n exact,\n });\n }\n\n clean += content.slice(last);\n return { content: clean, citations };\n}\n","/**\n * Run `worker` over `items` with at most `limit` of them in flight at once\n * (DETECTION-QUALITY-THROUGHPUT P6). Hand-rolled rather than a dependency: the\n * need is exactly one bounded map, and the BOUND is the whole point — detection\n * parallelizes independent entity types, but unbounded fan-out just trades\n * sequential waiting for provider 429 thrash, so the cap is the feature.\n *\n * A fixed pool of `min(limit, items.length)` pumps, each pulling the next index\n * until the work is exhausted — so at most `limit` calls are ever awaiting at\n * once, whatever the item count. Results are returned in INPUT order (a caller\n * can zip them back to items); COMPLETION order is not input order — that is the\n * point of concurrency — so a caller that must act \"as each finishes\" does so\n * inside `worker`, not on the returned array.\n *\n * Failure matches the sequential loop it replaces: the first `worker` rejection\n * rejects the whole run. Pumps already awaiting finish (they cannot be\n * cancelled mid-await), but no new item is pulled once a rejection propagates.\n */\nexport async function runBounded<T, R>(\n items: readonly T[],\n limit: number,\n worker: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let next = 0;\n\n async function pump(): Promise<void> {\n while (true) {\n const i = next++;\n if (i >= items.length) return;\n results[i] = await worker(items[i]!, i);\n }\n }\n\n const poolSize = Math.max(1, Math.min(limit, items.length));\n await Promise.all(Array.from({ length: poolSize }, () => pump()));\n return results;\n}\n","/**\n * Job Processors\n *\n * Pure functions that take content + inference client + params,\n * report progress via callback, and return annotations + results.\n *\n * No EventBus, no JobQueue, no side effects except calling inference.\n * Driven by the remote worker process (worker-process.ts), which claims\n * jobs over SSE and dispatches by jobType to these functions.\n */\n\nimport { AnnotationDetection } from './workers/annotation-detection';\nimport { extractEntities } from './workers/detection/entity-extractor';\nimport { DEFAULT_MAX_TOKENS, generateResourceFromTopic } from './workers/generation/resource-generation';\nimport { compileTypst, MAX_COMPILE_REPAIRS } from './workers/generation/typst-compiler';\nimport { withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';\nimport { resolveCitationTokens, collectContextResourceIds, type GenerationCitation } from './workers/generation/citation-resolver';\nimport { annotationIdFor } from '@semiont/event-sourcing';\nimport { didToAgent, GENERATABLE_MEDIA_TYPES, type Annotation, type GenerationJobParams, type Logger, type ResourceId, type SupportedMediaType, type components } from '@semiont/core';\nimport { reconcileSelector, createFragmentSelector, locate, type ReconciledSelector, type AnchoredText } from '@semiont/core';\nimport type { InferenceClient } from '@semiont/inference';\nimport type {\n HighlightDetectionParams,\n CommentDetectionParams,\n AssessmentDetectionParams,\n DetectionParams,\n TagDetectionParams,\n HighlightDetectionResult,\n CommentDetectionResult,\n AssessmentDetectionResult,\n DetectionResult,\n TagDetectionResult,\n GenerationResult,\n} from './types';\nimport { noteAnchor } from './workers/detection/anchor-audit';\nimport { runBounded } from './workers/detection/bounded-concurrency';\n\ntype Agent = components['schemas']['Agent'];\n\n/** A detected span — offsets into the extracted `.text`, plus optional context. */\nexport type SpanMatch = { exact: string; start: number; end: number; prefix?: string; suffix?: string };\n\n/**\n * The span half of an annotation's identity (JOB-RESTART-SAFETY P3).\n *\n * Shared by both builders because the span IS the same fact in both — the PDF\n * path additionally persists geometry for it, but that geometry is derived\n * from these offsets, so hashing it too would add no distinguishing power and\n * would make an id depend on a layout the text path cannot reproduce.\n *\n * `exact` is included, not just the offsets: after a content update the same\n * offsets cover different text, and that is a different annotation.\n */\nfunction spanAnchor(match: Pick<SpanMatch, 'start' | 'end' | 'exact'>): string {\n return `${match.start}:${match.end}:${match.exact}`;\n}\n\n/**\n * Turn a detected span into a stored annotation. The media type, resource, and\n * attribution context are closed over by the caller (see `prepareDetection`);\n * the detection processor supplies only the motivation, the span, and any\n * motivation-specific body. This is the single axis that varies by media type,\n * so the detection processors themselves stay media-agnostic.\n */\nexport type BuildAnnotation = (\n motivation: Motivation,\n match: SpanMatch,\n body?: Annotation['body'],\n) => Annotation;\n\n/**\n * Progress callback. The two positional args are the required `JobProgress`\n * fields (`percentage`, `message`). The third optional arg carries the\n * job-type-specific fields (`completedEntityTypes`, `requestParams`, etc.)\n * that the progress UI renders.\n *\n * Anything in `extra` describing the RUN rather than the moment must be passed\n * on EVERY call: the client's `progress$` replaces its value per event, so a\n * field sent once disappears on the next tick.\n *\n * `message` is a CODE plus typed params, never a prose sentence\n * (ASSIST-PROGRESS-CONSOLIDATION A6). The producer reports what happened;\n * each client renders it in the user's language — react-ui from its 29\n * locales, the Go launcher from its English map. The vocabulary is frozen\n * by the census of these call sites: adding a shape means adding a variant\n * to `JobProgressMessage.json` and copy in every client, not composing a\n * new sentence here.\n */\nexport type OnProgress = (\n percentage: number,\n message: JobProgressMessage,\n extra?: Partial<JobProgress>,\n) => void;\n\ntype JobProgress = components['schemas']['JobProgress'];\n/** Derived from the wire, never restated: the per-unit entry's shape is the\n * spec's, so a field added there reaches these accumulators by regeneration\n * rather than by someone remembering two places. */\ntype CompletedItem = NonNullable<JobProgress['completedItems']>[number];\ntype JobProgressMessage = components['schemas']['JobProgressMessage'];\n\n/** The five W3C motivations this system mints — a closed vocabulary, so it is\n * typed as one rather than as `string`. */\nexport type Motivation = Annotation['motivation'];\n\nexport interface ProcessorResult<R> {\n annotations: Annotation[];\n result: R;\n}\n\n/**\n * Strip the audit-only fields (`anchorMethod`, `llmOffsets`, `matchQuality`)\n * off a `ReconciledSelector` so the rest is shaped like a match input for\n * `buildTextAnnotation`. The audit info belongs in logs, not in storage.\n */\nfunction toMatch(r: ReconciledSelector): { exact: string; start: number; end: number; prefix?: string; suffix?: string } {\n return {\n exact: r.exact,\n start: r.start,\n end: r.end,\n ...(r.prefix !== undefined ? { prefix: r.prefix } : {}),\n ...(r.suffix !== undefined ? { suffix: r.suffix } : {}),\n };\n}\n\n/**\n * Identity key for a built annotation: motivation + anchored span + body.\n * Two annotations with the same key are the same event written twice.\n */\nfunction annotationDedupeKey(ann: Record<string, unknown>): string {\n const target = ann.target as\n | { selector?: Array<{ type: string; start?: number; end?: number; value?: string; exact?: string; prefix?: string; suffix?: string }> }\n | undefined;\n const selectors = Array.isArray(target?.selector) ? target.selector : [];\n const pos = selectors.find((s) => s.type === 'TextPositionSelector');\n // Anchor identity is media-specific. Text annotations carry a\n // TextPositionSelector (durable char offsets). PDF annotations have none —\n // their anchor is the per-line FragmentSelector viewrect geometry plus the\n // TextQuoteSelector text. Keying only on TextPositionSelector would collapse\n // every PDF annotation sharing a motivation+body onto one (its offsets fall\n // back to '?'), so e.g. multiple PDF highlights emit as a single annotation.\n let anchor: string;\n if (pos) {\n anchor = `pos:${pos.start ?? '?'}:${pos.end ?? '?'}`;\n } else {\n const frags = selectors.filter((s) => s.type === 'FragmentSelector').map((s) => s.value ?? '').join(',');\n const quote = selectors.find((s) => s.type === 'TextQuoteSelector');\n anchor = `frag:${frags}|quote:${quote?.exact ?? ''}:${quote?.prefix ?? ''}:${quote?.suffix ?? ''}`;\n }\n return [ann.motivation as string, anchor, JSON.stringify(ann.body ?? null)].join('|');\n}\n\n/**\n * Drop annotations that are identical in the fields that define an\n * annotation's meaning: motivation, anchored span, and body.\n *\n * Why this is needed: each LLM-emitted span is reconciled independently\n * (no cross-entry coordination), and `reconcileSelector`'s `first-of-many`\n * fallback anchors every undisambiguated entry at the *same* first\n * occurrence. So a phrase repeated in non-distinctive context can produce\n * several entries that all collapse onto one span — identical events. This\n * collapses them back to one.\n *\n * What it does NOT drop: same span, *different* body (e.g. the same text\n * tagged as two entity types, or two distinct comments on one passage).\n * Those are legitimately distinct annotations.\n *\n * Applied identically by every processor below.\n */\nfunction dedupeAnnotations(annotations: Annotation[]): Annotation[] {\n const seen = new Set<string>();\n const out: Annotation[] = [];\n for (const ann of annotations) {\n const key = annotationDedupeKey(ann);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(ann);\n }\n return out;\n}\n\nexport function buildTextAnnotation(\n content: string,\n resourceId: ResourceId,\n userId: string,\n generator: Agent,\n motivation: Motivation,\n match: { exact: string; start: number; end: number; prefix?: string; suffix?: string },\n // Body may be a single AnnotationBody object or a non-empty array of\n // them, OR omitted entirely. W3C treats body as optional; annotations\n // whose motivation alone conveys meaning (highlighting) legitimately\n // skip it. Every other motivation currently passes something; the\n // processor that calls this makes the choice per-motivation.\n body?: Annotation['body'],\n) {\n // Write-time invariant. Every selector that reaches storage must be\n // internally consistent with the source content. If a worker bypasses\n // `reconcileSelector` or a future change re-introduces overlap, the\n // throw fires loudly here instead of corrupting the KB.\n if (content.substring(match.start, match.end) !== match.exact) {\n throw new Error(\n `buildTextAnnotation invariant: content.substring(${match.start}, ${match.end}) !== exact ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n if (match.prefix !== undefined) {\n const actualPrefix = content.substring(Math.max(0, match.start - match.prefix.length), match.start);\n if (actualPrefix !== match.prefix) {\n throw new Error(\n `buildTextAnnotation invariant: content prefix-slice !== prefix ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n }\n if (match.suffix !== undefined) {\n const actualSuffix = content.substring(match.end, Math.min(content.length, match.end + match.suffix.length));\n if (actualSuffix !== match.suffix) {\n throw new Error(\n `buildTextAnnotation invariant: content suffix-slice !== suffix ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n }\n\n // `userId` here is the DID of the human who initiated the work. The\n // worker process is acting on their behalf using `generator` to\n // produce content. Per the protocol attribution model:\n // creator = who initiated (the human)\n // generator = what produced (the software peer)\n // wasAttributedTo = both parties (PROV-O)\n // For autonomous-agent work creator and generator collapse to the\n // same Software Agent; the same field assignments still hold.\n const creator = didToAgent(userId);\n const wasAttributedTo: Agent[] =\n creator['@id'] === generator['@id'] ? [generator] : [creator, generator];\n return {\n '@context': 'http://www.w3.org/ns/anno.jsonld' as const,\n 'type': 'Annotation' as const,\n 'id': annotationIdFor({ resourceId: resourceId as string, motivation, anchor: spanAnchor(match), body }),\n motivation,\n creator,\n generator,\n wasAttributedTo,\n created: new Date().toISOString(),\n target: {\n type: 'SpecificResource' as const,\n source: resourceId as string,\n selector: [\n { type: 'TextPositionSelector' as const, start: match.start, end: match.end },\n {\n type: 'TextQuoteSelector' as const,\n exact: match.exact,\n ...(match.prefix && { prefix: match.prefix }),\n ...(match.suffix && { suffix: match.suffix }),\n },\n ],\n },\n ...(body !== undefined ? { body } : {}),\n };\n}\n\n/**\n * PDF sibling of `buildTextAnnotation`. The model returns the same\n * `{ exact, start, end, prefix?, suffix? }` match over the extracted text\n * layer's `text`; geometry comes from the layer, never the model.\n *\n * `target.selector` = one `FragmentSelector` per line (`locate` unions the\n * overlapping text-layer items into per-line viewrects) plus a\n * `TextQuoteSelector` anchor. No `TextPositionSelector`: the extracted text\n * layer is a derived artifact, not the stored content, so its char offsets are\n * not a durable anchor.\n *\n * Write-time invariant (geometry <-> text): geometry is item-level (word runs),\n * so the covered items' text must *contain* `exact` (whitespace-normalized) —\n * containment, not reconstruction. An empty cover (no overlapping items -> no\n * rects) also fails. Throws loudly, naming the resource + motivation, rather\n * than persisting geometry that doesn't back the quoted text.\n */\nexport function buildPdfAnnotation(\n anchored: AnchoredText,\n resourceId: ResourceId,\n userId: string,\n generator: Agent,\n motivation: Motivation,\n match: { exact: string; start: number; end: number; prefix?: string; suffix?: string },\n body?: Annotation['body'],\n) {\n // `locate` returns both the per-line rects and the overlap items it found;\n // reuse `overlap` for the containment check rather than re-scanning layer.items.\n const { rects, overlap } = locate(anchored, match.start, match.end);\n\n const coveredText = overlap.length\n ? anchored.text.substring(\n Math.min(...overlap.map((i) => i.start)),\n Math.max(...overlap.map((i) => i.end)),\n )\n : '';\n const normalize = (s: string) => s.replace(/\\s+/g, ' ').trim();\n if (rects.length === 0 || !normalize(coveredText).includes(normalize(match.exact))) {\n throw new Error(\n `buildPdfAnnotation invariant: covered text does not contain exact ` +\n `for resource ${resourceId}, motivation ${motivation}`,\n );\n }\n\n const creator = didToAgent(userId);\n const wasAttributedTo: Agent[] =\n creator['@id'] === generator['@id'] ? [generator] : [creator, generator];\n\n return {\n '@context': 'http://www.w3.org/ns/anno.jsonld' as const,\n 'type': 'Annotation' as const,\n 'id': annotationIdFor({ resourceId: resourceId as string, motivation, anchor: spanAnchor(match), body }),\n motivation,\n creator,\n generator,\n wasAttributedTo,\n created: new Date().toISOString(),\n target: {\n type: 'SpecificResource' as const,\n source: resourceId as string,\n selector: [\n ...rects.map((coord) => ({\n type: 'FragmentSelector' as const,\n conformsTo: 'http://tools.ietf.org/rfc/rfc3778' as const,\n value: createFragmentSelector(coord),\n })),\n {\n type: 'TextQuoteSelector' as const,\n exact: match.exact,\n ...(match.prefix && { prefix: match.prefix }),\n ...(match.suffix && { suffix: match.suffix }),\n },\n ],\n },\n ...(body !== undefined ? { body } : {}),\n };\n}\n\nexport async function processHighlightJob(\n content: string,\n inferenceClient: InferenceClient,\n params: HighlightDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<HighlightDetectionResult>> {\n const echo = detectionEcho(params);\n\n onProgress(10, { code: 'loading' }, echo);\n onProgress(30, { code: 'analyzing' }, echo);\n\n const highlights = await AnnotationDetection.detectHighlights(\n content, inferenceClient, params.instructions, params.density, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.\n (completed, total) => onProgress(30 + Math.round((completed / total) * 30), { code: 'analyzing' }, echo),\n );\n\n onProgress(60, { code: 'creating-annotations', count: highlights.length }, echo);\n\n // Highlights carry no body — motivation:'highlighting' on a target\n // is a complete annotation per the W3C Web Annotation Model.\n const annotations = dedupeAnnotations(highlights.map((h) =>\n buildAnnotation('highlighting', h),\n ));\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'highlight' }, echo);\n\n return {\n annotations,\n result: { kind: 'highlight-annotation', highlightsFound: highlights.length, highlightsCreated: annotations.length },\n };\n}\n\n/**\n * The user's own inputs, echoed back for the progress widget. Labels are CODES\n * (the client localizes them); values are the user's words and are shown\n * verbatim. Absent or blank inputs are omitted rather than rendered as empty\n * rows — \"Instructions:\" with nothing after it is noise, not information.\n *\n * Returned as the `extra` object rather than a bare array because every\n * `onProgress` in the run passes it: `progress$` REPLACES its value per event\n * (mark-state-unit), so a field sent once would flash at 10% and disappear.\n * The parameters describe the whole run, so every event carries them — the\n * same convention `processReferenceJob` already follows for its entity types.\n */\nfunction detectionEcho(p: {\n instructions?: string;\n tone?: string;\n density?: number;\n}): Partial<JobProgress> {\n const requestParams: Array<{ label: 'instructions' | 'tone' | 'density'; value: string }> = [];\n if (p.instructions?.trim()) requestParams.push({ label: 'instructions', value: p.instructions.trim() });\n if (p.tone?.trim()) requestParams.push({ label: 'tone', value: p.tone.trim() });\n if (p.density !== undefined) requestParams.push({ label: 'density', value: String(p.density) });\n return requestParams.length > 0 ? { requestParams } : {};\n}\n\nexport async function processCommentJob(\n content: string,\n inferenceClient: InferenceClient,\n params: CommentDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<CommentDetectionResult>> {\n const echo = detectionEcho(params);\n\n onProgress(10, { code: 'loading' }, echo);\n onProgress(30, { code: 'analyzing' }, echo);\n\n const comments = await AnnotationDetection.detectComments(\n content, inferenceClient, params.instructions, params.tone, params.density,\n params.language, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.\n (completed, total) => onProgress(30 + Math.round((completed / total) * 30), { code: 'analyzing' }, echo),\n );\n\n onProgress(60, { code: 'creating-annotations', count: comments.length }, echo);\n\n // The body's `language` reflects the locale the LLM was asked to write in\n // (`params.language` — the user's UI locale). Defaults to 'en' when the\n // caller didn't specify, matching what the LLM produces by default.\n const bodyLanguage = params.language ?? 'en';\n const annotations = dedupeAnnotations(comments.map((c) =>\n // Match the pre-#651 CommentAnnotationWorker: include format and\n // language on the body TextualBody. Optional in the schema, but\n // consumers that do language-aware rendering rely on them.\n buildAnnotation('commenting', c, [\n { type: 'TextualBody', value: c.comment, purpose: 'commenting', format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage },\n ]),\n ));\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'comment' }, echo);\n\n return {\n annotations,\n result: { kind: 'comment-annotation', commentsFound: comments.length, commentsCreated: annotations.length },\n };\n}\n\nexport async function processAssessmentJob(\n content: string,\n inferenceClient: InferenceClient,\n params: AssessmentDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<AssessmentDetectionResult>> {\n const echo = detectionEcho(params);\n\n onProgress(10, { code: 'loading' }, echo);\n onProgress(30, { code: 'analyzing' }, echo);\n\n const assessments = await AnnotationDetection.detectAssessments(\n content, inferenceClient, params.instructions, params.tone, params.density,\n params.language, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.\n (completed, total) => onProgress(30 + Math.round((completed / total) * 30), { code: 'analyzing' }, echo),\n );\n\n onProgress(60, { code: 'creating-annotations', count: assessments.length }, echo);\n\n const bodyLanguage = params.language ?? 'en';\n const annotations = dedupeAnnotations(assessments.map((a) =>\n // Single-object body with purpose aligned to motivation, matching the\n // pre-#651 AssessmentAnnotationWorker's shape and the majority of\n // persisted assessments. Do not switch to an array or to\n // purpose='describing' — that loses the \"this is an assessment, not\n // a description\" signal and breaks existing readers that access\n // `body.value` directly on the object.\n buildAnnotation('assessing', a, {\n type: 'TextualBody', value: a.assessment, purpose: 'assessing', format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage,\n }),\n ));\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'assessment' }, echo);\n\n return {\n annotations,\n result: { kind: 'assessment-annotation', assessmentsFound: assessments.length, assessmentsCreated: annotations.length },\n };\n}\n\n/**\n * Reference detection commits per UNIT — one entity type — through\n * `onUnitComplete` (ABANDONED-INFERENCE P2, checkpointed resume): the\n * callback receives the unit's deduped annotations, and only after it\n * resolves does the unit count as complete. Emission belongs to the\n * callback alone; the processor returns only the result — returning the\n * annotations as well would recreate the post-run batch that N2 showed\n * discards completed work wholesale.\n */\nexport async function processReferenceJob(\n content: string,\n inferenceClient: InferenceClient,\n params: DetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n logger: Logger,\n onUnitComplete: (entityType: string, annotations: Annotation[]) => Promise<void>,\n signal?: AbortSignal,\n): Promise<{ result: DetectionResult }> {\n const entityTypeNames = params.entityTypes.map(String);\n const requestParams = [{ label: 'entity-types' as const, value: entityTypeNames.join(', ') }];\n const completedItems: CompletedItem[] = [];\n let totalFound = 0;\n let totalEmitted = 0;\n let errors = 0;\n\n onProgress(10, { code: 'loading' }, { requestParams });\n\n const bodyLanguage = params.language ?? 'en';\n\n // Entity types run BOUNDED-CONCURRENT (DETECTION-QUALITY-THROUGHPUT P6).\n // They are independent units — own extraction, own commit, own checkpoint —\n // and a single sequential job used a sliver of the provider's rate limit, so\n // the old `for … await` was the 9×-sequential ≈ 2.5 h/document. The bound is\n // the point: unbounded fan-out just trades sequential waiting for 429 thrash.\n //\n // Shared counters and `completedItems` are mutated SYNCHRONOUSLY between\n // awaits inside the worker — safe under the single-threaded event loop (no\n // read-modify-write straddles an await), so no locking is needed. Progress is\n // now \"M of N done\" rather than \"on type i\": concurrent types finish out of\n // order, and `completedItems` already tolerates that.\n let completed = 0;\n const total = entityTypeNames.length;\n\n const emitTypeProgress = (entityTypeName: string): void => {\n const pct = 20 + Math.round((completed / total) * 60);\n onProgress(pct, { code: 'detecting-entities', entityType: entityTypeName }, {\n current: { kind: 'entity-type', value: entityTypeName },\n processed: completed,\n total,\n entitiesFound: totalFound,\n entitiesEmitted: totalEmitted,\n completedItems: [...completedItems],\n requestParams,\n });\n };\n\n // Concurrency is the PROVIDER's capability, not a flat constant: a hosted\n // API parallelizes, a local single-model server does not (P6). Reading it\n // from the client is what makes the same code correct on both.\n await runBounded(entityTypeNames, inferenceClient.maxConcurrency, async (entityTypeName) => {\n if (!entityTypeName) return;\n // Cooperative cancellation (JOB-RESTART-SAFETY P4): once aborted, a pending\n // type is skipped when its turn comes; types already in flight finish and\n // commit, so committed units stay checkpointed. The caller reads\n // `signal.aborted` to move the job to cancelled/ rather than complete/.\n if (signal?.aborted) return;\n\n emitTypeProgress(entityTypeName);\n\n const extractedEntities = await extractEntities(\n content, [entityTypeName], inferenceClient, params.includeDescriptiveReferences ?? false, logger,\n params.sourceLanguage,\n // Liveness heartbeat (DETECTION-HEARTBEAT): fires at chunk boundaries and\n // every ~15 s while a call is in flight, so a long single-chunk call is\n // not silent. It repeats the current position rather than inventing an\n // advance — the stall watchdog, janitor and client timeout need a signal,\n // not a monotone.\n () => emitTypeProgress(entityTypeName),\n );\n\n // Unresolved reference body: the entity type as a tagging TextualBody,\n // stamped with the body locale to match the comment/assess/tag pattern.\n // The bind flow later appends a SpecificResource (purpose: 'linking') via\n // mark:body-updated to produce the resolved shape. Emitting an empty body\n // would break the append contract.\n const unresolvedBody: Annotation['body'] = [\n { type: 'TextualBody' as const, value: entityTypeName, purpose: 'tagging' as const, format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage },\n ];\n\n const built: Annotation[] = [];\n for (const entity of extractedEntities) {\n const reconciled = reconcileSelector(content, {\n exact: entity.exact,\n ...(entity.prefix !== undefined ? { prefix: entity.prefix } : {}),\n ...(entity.suffix !== undefined ? { suffix: entity.suffix } : {}),\n });\n if (!reconciled) {\n logger.error('Entity dropped — text not found in source', {\n text: entity.exact,\n entityType: entity.entityType,\n });\n errors++;\n continue;\n }\n noteAnchor('reference', entity.exact, reconciled.anchorMethod, logger);\n const ann = buildAnnotation('linking', toMatch(reconciled), unresolvedBody);\n built.push(ann);\n }\n\n // De-dupe within the unit, then COMMIT: the awaited callback emits the\n // unit's annotations, and only after it resolves does the unit count\n // anywhere — a unit whose commit fails contributes nothing (unit\n // atomicity, A3).\n const unitAnnotations = dedupeAnnotations(built);\n await onUnitComplete(entityTypeName, unitAnnotations);\n totalEmitted += unitAnnotations.length;\n totalFound += extractedEntities.length;\n // Found vs persisted, per unit: the model proposed `extractedEntities`, the\n // log holds `unitAnnotations` after dedupe and the commit ack. The gap\n // between them is this flow's yield (DETECTION-QUALITY-THROUGHPUT P1).\n completedItems.push({\n value: entityTypeName,\n foundCount: extractedEntities.length,\n persistedCount: unitAnnotations.length,\n });\n completed++;\n emitTypeProgress(entityTypeName);\n });\n\n // The terminal frame carries the completed set, and it is the only frame\n // that can: the per-unit entries ride the progress emitted at the START of\n // each unit, so the LAST unit's entry — and on a single-type job, every\n // entry — was never reported anywhere (DETECTION-QUALITY-THROUGHPUT P1).\n onProgress(100, { code: 'complete-created', count: totalEmitted, kind: 'reference' }, {\n completedItems: [...completedItems],\n requestParams,\n });\n\n return {\n result: { kind: 'reference-annotation', totalFound, totalEmitted, errors },\n };\n}\n\nexport async function processTagJob(\n content: string,\n inferenceClient: InferenceClient,\n params: TagDetectionParams,\n buildAnnotation: BuildAnnotation,\n onProgress: OnProgress,\n): Promise<ProcessorResult<TagDetectionResult>> {\n onProgress(10, { code: 'loading' });\n onProgress(30, { code: 'analyzing-tags' });\n\n const allTags = [];\n const completedItems: CompletedItem[] = [];\n for (let c = 0; c < params.categories.length; c++) {\n const category = params.categories[c]!;\n // The loop always existed; it just never reported itself, so the tag flow\n // was the one counting flow with no subject line (CLEAN-PROGRESS A5).\n const position = () => ({\n current: { kind: 'category' as const, value: category },\n processed: c,\n total: params.categories.length,\n completedItems: [...completedItems],\n });\n onProgress(\n 30 + Math.round((c / params.categories.length) * 30),\n { code: 'analyzing-tags' },\n position(),\n );\n const categoryTags = await AnnotationDetection.detectTags(\n content, inferenceClient, params.schema, category, params.sourceLanguage,\n // Liveness (chunk boundaries + in-flight heartbeat): this category's\n // slice of the 30–60 band.\n (completed, total) => onProgress(\n 30 + Math.round(((c + completed / total) / params.categories.length) * 30),\n { code: 'analyzing-tags' },\n position(),\n ),\n );\n completedItems.push({ value: category, foundCount: categoryTags.length });\n allTags.push(...categoryTags);\n }\n const tags = allTags;\n\n onProgress(60, { code: 'creating-tag-annotations', count: tags.length });\n\n const bodyLanguage = params.language ?? 'en';\n const annotations = dedupeAnnotations(tags.map((t) => {\n const category = t.category ?? 'unknown';\n // Two-body shape matches the pre-#651 TagAnnotationWorker and every\n // persisted tag annotation: the category as a tagging TextualBody,\n // plus the tagging-schema id as a classifying TextualBody. The\n // classifying body is the only trace of schema provenance in the\n // event log — do not drop it.\n return buildAnnotation('tagging', t, [\n { type: 'TextualBody', value: category, purpose: 'tagging', format: 'text/plain' satisfies SupportedMediaType, language: bodyLanguage },\n { type: 'TextualBody', value: params.schema.id, purpose: 'classifying', format: 'text/plain' satisfies SupportedMediaType },\n ]);\n }));\n\n // byCategory is computed from the *deduped* set so the per-category\n // counts match what's actually stored. The category is the first\n // (tagging) TextualBody's value.\n const byCategory: Record<string, number> = {};\n for (const ann of annotations) {\n const body = (ann as { body?: Array<{ value?: unknown }> }).body;\n const category = Array.isArray(body) && typeof body[0]?.value === 'string' ? body[0].value : 'unknown';\n byCategory[category] = (byCategory[category] ?? 0) + 1;\n }\n\n onProgress(100, { code: 'complete-created', count: annotations.length, kind: 'tag' });\n\n return {\n annotations,\n result: { kind: 'tag-annotation', tagsFound: tags.length, tagsCreated: annotations.length, byCategory },\n };\n}\n\n/**\n * Output bound (PDF-GENERATION P5), symmetric with #1124's extraction budget\n * and deliberately the SAME threshold: an artifact larger than what extraction\n * accepts would be a resource our own Smelter declines as 'too-large'. One\n * judgment, two enforcement points. A runaway generation fails loudly\n * (job:fail); it never uploads.\n */\nexport function assertWithinOutputBudget(byteLength: number): void {\n if (!withinByteBudget(byteLength)) {\n throw new Error(\n `Generated artifact exceeds the output byte budget: ${byteLength} bytes > ${MAX_PDF_BYTES}. Refusing a runaway generation.`,\n );\n }\n}\n\nexport async function processGenerationJob(\n inferenceClient: InferenceClient,\n params: GenerationJobParams,\n onProgress: OnProgress,\n logger: Logger,\n): Promise<{ content: Uint8Array; title: string; format: SupportedMediaType; citations: GenerationCitation[]; result: GenerationResult }> {\n // Refuse any requested media type the registry doesn't mark `generatable` —\n // loudly (the throw propagates as job:fail), never a silent markdown fallback\n // under a mislabeled format. The gate reads the registry capability\n // (PDF-GENERATION P1), not a local table. Validate before the LLM call.\n const outputMediaType: SupportedMediaType = params.outputMediaType ?? 'text/markdown';\n if (!GENERATABLE_MEDIA_TYPES.includes(outputMediaType)) {\n throw new Error(\n `Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(' or ')}.`,\n );\n }\n\n const title = params.title ?? 'Untitled';\n const entityTypes = (params.entityTypes ?? []).map(String);\n\n // PDF path (PDF-GENERATION P3): the model authors Typst; the worker's pinned\n // binary compiles it, with the legible compile errors fed back for a bounded\n // number of repairs. Citations on PDFs need page geometry, not text offsets —\n // until the citation branch (P4) provides it, `cite` fails fast and loudly\n // rather than minting selectors that would render nothing.\n if (outputMediaType === 'application/pdf') {\n onProgress(5, { code: 'generating-resource' });\n\n // Under `cite`, [[<id>]] tokens are stripped from the SOURCE before every\n // compile — they must never render into the artifact. The citations carry\n // the claim text; the worker re-anchors it by page geometry after\n // extraction (P4). Offsets in these citations index the Typst source and\n // are NOT used for PDF anchoring.\n const validIds = params.cite === true ? collectContextResourceIds(params.context) : null;\n\n let generated = await generateResourceFromTopic(\n title, entityTypes, inferenceClient, logger,\n params.prompt, params.language, params.context, params.temperature,\n params.maxTokens, params.sourceLanguage, outputMediaType,\n params.task, params.structure, params.cite,\n );\n let source = generated.content;\n let citations: GenerationCitation[] = [];\n if (validIds) {\n const resolved = resolveCitationTokens(generated.content, validIds, logger);\n source = resolved.content;\n citations = resolved.citations;\n }\n let compiled = compileTypst(source);\n let repairs = 0;\n while ('error' in compiled) {\n // A truncated source that fails to compile is cut off, not wrong —\n // every repair regenerates under the same ceiling and cannot restore\n // content that was never generated. Fail immediately with the actual\n // cause instead of burning the repair budget and blaming the compiler\n // (GENERATE-FROM-RESOURCE P3a).\n if (generated.truncated) {\n throw new Error(\n `Generation stopped at the maxTokens ceiling (${params.maxTokens ?? DEFAULT_MAX_TOKENS} tokens) and the cut-off Typst source does not compile — repair cannot help; raise maxTokens. Compile error: ${compiled.error}`,\n );\n }\n if (repairs >= MAX_COMPILE_REPAIRS) {\n throw new Error(\n `Typst compilation failed after ${MAX_COMPILE_REPAIRS} repair attempts: ${compiled.error}`,\n );\n }\n repairs++;\n logger.warn('Typst compile failed — feeding the error back for repair', {\n attempt: repairs,\n error: compiled.error.slice(0, 500),\n });\n generated = await generateResourceFromTopic(\n title, entityTypes, inferenceClient, logger,\n params.prompt, params.language, params.context, params.temperature,\n params.maxTokens, params.sourceLanguage, outputMediaType,\n params.task, params.structure, params.cite,\n { source, error: compiled.error },\n );\n if (validIds) {\n const resolved = resolveCitationTokens(generated.content, validIds, logger);\n source = resolved.content;\n citations = resolved.citations;\n } else {\n source = generated.content;\n }\n compiled = compileTypst(source);\n }\n\n assertWithinOutputBudget(compiled.pdf.byteLength);\n onProgress(95, { code: 'creating-resource' });\n // The producer owns terminality (GENERATE-FROM-RESOURCE P1/D1): without\n // this, the client's last frame is forever the 95% payload. Generic by\n // design — the outcome (name + link) travels on job:complete (D8).\n // `truncated` rides both surfaces (P3a/D6): truncation that lands at a\n // syntactic boundary still compiles, and the artifact is still cut off.\n onProgress(100, { code: 'complete-generated', truncated: generated.truncated });\n\n return {\n content: compiled.pdf,\n title,\n format: outputMediaType,\n citations,\n result: {\n kind: 'generation',\n resourceId: '' as ResourceId,\n resourceName: title,\n truncated: generated.truncated,\n },\n };\n }\n\n // Generation has exactly two observable transitions: the LLM call starting\n // ('generating') and content finalized / creation beginning ('creating').\n // There is no fetch — context arrives pre-gathered in params. Percentages\n // approximate the share of expected wall-clock complete at each transition\n // (a single atomic LLM call has no measurable progress, and inference\n // dominates the job): its start is ~5, its end ~95.\n onProgress(5, { code: 'generating-resource' });\n\n const generated = await generateResourceFromTopic(\n title,\n entityTypes,\n inferenceClient,\n logger,\n params.prompt,\n params.language,\n params.context,\n params.temperature,\n params.maxTokens,\n params.sourceLanguage,\n outputMediaType,\n params.task,\n params.structure,\n params.cite,\n );\n\n // Under `cite`, the model emitted [[<id>]] transport tokens — resolve them:\n // validate against the ids the context actually contained, strip from the\n // stored content, and carry claim-span citations for the worker to mint.\n // When cite is off, bracketed text is legitimate content — leave it alone.\n let content = generated.content;\n let citations: GenerationCitation[] = [];\n if (params.cite === true) {\n const resolved = resolveCitationTokens(content, collectContextResourceIds(params.context), logger);\n content = resolved.content;\n citations = resolved.citations;\n }\n\n onProgress(95, { code: 'creating-resource' });\n\n // The artifact is bytes; text is an encoding of them. One shape for every\n // output media type, so a string can never travel mislabeled as a binary\n // format (PDF-GENERATION P1). Citation offsets index the decoded text.\n const artifact = new TextEncoder().encode(content);\n assertWithinOutputBudget(artifact.byteLength);\n\n // The producer owns terminality (GENERATE-FROM-RESOURCE P1/D1): without\n // this, the client's last frame is forever the 95% payload. Generic by\n // design — the outcome (name + link) travels on job:complete (D8).\n // `truncated` rides both surfaces (P3a/D6): the event is the frame the\n // client renders, the result is the record.\n onProgress(100, { code: 'complete-generated', truncated: generated.truncated });\n\n return {\n content: artifact,\n title,\n format: outputMediaType,\n citations,\n result: {\n kind: 'generation',\n resourceId: '' as ResourceId,\n resourceName: title,\n truncated: generated.truncated,\n },\n };\n}\n","/**\n * Worker Runtime — the importable half of the worker host.\n *\n * `worker-main.ts` is a process entrypoint (config at module scope,\n * `main()` at import) and therefore untestable by construction; everything\n * a unit test needs to reach lives here instead, fully parameterized — no\n * module-scope env reads, no side effects at import.\n *\n * The load-bearing contract this module owns (and the reason it was\n * extracted): a worker's stamped identity is the DID the\n * `/api/tokens/agent` exchange MINTED for it, carried verbatim — never\n * re-derived from the URL the worker happens to dial. One logical agent\n * previously got two DIDs that way (.plans/bugs/agent-did-host-skew.md).\n */\n\nimport { startWorkerProcess } from './worker-process';\nimport type { MarkCommitAwaits, DescriptorReadAwaits } from './worker-process';\nimport type { WorkerVitals, JobClaimAwaits } from './job-claim-adapter';\nimport type { ConsultAnchoredTextAwaits } from './workers/detection/prepare-detection';\nimport type { InferenceClient } from '@semiont/inference';\nimport { hostname } from 'os';\nimport {\n replyChannelsFor,\n didToAgent,\n baseUrl,\n retryWithBackoff,\n isTransientFetchError,\n STARTUP_FETCH_RETRY,\n type BusOperationKey,\n type RetryPolicy,\n type components,\n type Logger,\n type AccessToken,\n} from '@semiont/core';\nimport {\n InMemorySessionStorage,\n SemiontClient,\n SemiontSession,\n kbGatewayUrl,\n setStoredSession,\n type HttpEndpoint,\n type KbTarget,\n} from '@semiont/sdk';\nimport { HttpContentTransport, HttpTransport } from '@semiont/http-transport';\nimport type { ContentReads } from '@semiont/content';\nimport { BehaviorSubject } from 'rxjs';\n\ntype Agent = components['schemas']['Agent'];\n\n/** Shape of each resolved worker inference entry under `_metadata.workers`. */\nexport type ResolvedInference = {\n type: 'anthropic' | 'ollama';\n model: string;\n apiKey?: string;\n endpoint?: string;\n baseURL?: string;\n};\n\n/** One agent identity: an inference engine and the job types it serves. */\nexport interface AgentGroup {\n inference: ResolvedInference;\n jobTypes: string[];\n client: InferenceClient;\n}\n\nexport interface WorkerRuntimeOptions {\n group: AgentGroup;\n /** The gateway URL this worker dials — connection topology ONLY, never identity. */\n gatewayBaseUrl: string;\n /** Shared secret for `/api/tokens/agent`, and the bearer the byte reads\n * below show the Archivist. */\n workerSecret: string;\n /**\n * The resource's bytes, for detection's extraction seam. Built by the\n * entrypoint (`worker-main`) rather than here, so a worker with no\n * Archivist configured refuses at boot instead of failing every job\n * (SINGLE-KB-MOUNT P4).\n */\n contentReads: ContentReads;\n logger: Logger;\n}\n\n/** Per-agent liveness: the adapter's snapshot plus this agent's identity. */\nexport interface AgentVitals extends WorkerVitals {\n provider: string;\n model: string;\n did: string;\n jobTypes: string[];\n}\n\nexport interface AgentWorkerHandle {\n session: SemiontSession;\n vitals(): AgentVitals;\n dispose(): Promise<void>;\n}\n\nexport interface WorkerHealthPayload {\n status: 'ok';\n agents: number;\n workers: AgentVitals[];\n}\n\n/**\n * The `/health` body (WORKER-LIVENESS.md P1). Additive: existing\n * consumers (image HEALTHCHECK, compose `service_healthy`, `semiont start`)\n * keep reading `status`/`agents`; the per-agent vitals expose\n * claim-loop progress so a stalled worker is *visible*, not just alive.\n */\nexport function buildHealthPayload(workers: ReadonlyArray<{ vitals(): AgentVitals }>): WorkerHealthPayload {\n return {\n status: 'ok',\n agents: workers.length,\n workers: workers.map((w) => w.vitals()),\n };\n}\n\n/**\n * Stall watchdog (WORKER-LIVENESS.md P3) — the fail-fast line behind the\n * inference timeout. There is no poll loop to heartbeat; the honest\n * stall signal in this push-driven architecture is *processing without\n * activity*: an agent holding a claimed job whose `lastActivityAt`\n * (claim / progress / finish) has stopped advancing is wedged — the\n * adapter ignores every announcement while `isProcessing`, so a wedged\n * agent never recovers on its own. Silent hang → loud crash → whatever\n * restart policy the deployment chose.\n *\n * Thresholds are fixed by design (no env knobs) and deliberately\n * layered: inference timeout (10 min, P2) fires first; this watchdog\n * (15 min) catches wedges where the loop still turns but activity has\n * stopped; the gateway's dead-worker janitor (30 min) re-queues the job\n * regardless.\n *\n * The layering matters because this watchdog has a hard limit: it is an\n * IN-PROCESS timer, so it cannot fire while the event loop itself is\n * blocked — the exact condition a blocked loop creates\n * (JOB-RESTART-SAFETY P7, the 2026-09-03 finalization hang: 18 min silent,\n * this watchdog never fired, an empty /health confirming the loop was\n * wedged). The unbounded emit that caused that specific hang is now bounded\n * at the transport (`EMIT_TIMEOUT_MS`), so the loop errors instead of\n * blocking; but for any future blocked-loop bug the ONLY backstop is the\n * out-of-process one — the gateway's janitor sweeping this worker's job\n * files by mtime (`fs-job-queue.ts` `recoverStaleRunningJobs`). A liveness\n * guarantee a blocked loop defeats is not one; the janitor is the guarantee.\n */\nexport const STALL_THRESHOLD_MS = 15 * 60_000;\nexport const STALL_CHECK_INTERVAL_MS = 60_000;\n\nexport interface StallWatchdogOptions {\n workers: ReadonlyArray<{ vitals(): AgentVitals }>;\n logger: Logger;\n /** Test seam; defaults to process.exit. */\n exit?: (code: number) => void;\n}\n\nexport function startStallWatchdog(opts: StallWatchdogOptions): { dispose(): void } {\n const { workers, logger, exit = (code: number) => process.exit(code) } = opts;\n\n const timer = setInterval(() => {\n const now = Date.now();\n for (const worker of workers) {\n const v = worker.vitals();\n if (!v.activeJob || !v.lastActivityAt) continue;\n\n const silentForMs = now - Date.parse(v.lastActivityAt);\n if (silentForMs <= STALL_THRESHOLD_MS) continue;\n\n logger.error('Worker stalled — exiting for restart', {\n provider: v.provider,\n model: v.model,\n did: v.did,\n jobId: v.activeJob.jobId,\n jobType: v.activeJob.type,\n processingSince: v.activeJob.since,\n lastActivityAt: v.lastActivityAt,\n silentForMs,\n thresholdMs: STALL_THRESHOLD_MS,\n });\n clearInterval(timer);\n exit(1);\n return;\n }\n }, STALL_CHECK_INTERVAL_MS);\n timer.unref?.();\n\n return { dispose: () => clearInterval(timer) };\n}\n\n/**\n * The bus operations a worker process ever AWAITS a reply to. Reply channels\n * are global fan-out on the gateway, so a full `BRIDGED_CHANNELS`\n * subscription made every worker receive every other client's reply traffic\n * — measured at ~85 multi-MB `browse:annotations-result` frames/min during\n * the 2026-09-03 worker OOM, all parsed and dropped by cid filtering. The\n * worker subscribes exactly its own operations' reply channels instead.\n *\n * This list restates a fact the code owns (which operations worker code\n * paths call `busRequest` on); its gate is the build-time census below\n * (`workerAwaitCensus`): every awaiting site declares its operation next to\n * the call, and an operation awaited but missing here fails COMPILATION with\n * the operation named. `busRequest`'s `isSubscribed` probe remains the\n * runtime backstop for an await nobody declared — a loud `bus.unsubscribed`\n * at first use, never a silent 30 s timeout.\n * (`job:queued` is not here: it is a broadcast, added by the claim\n * adapter via `addChannels`.)\n */\nexport const WORKER_AWAITED_OPERATIONS = [\n 'job:claim',\n 'browse:resource-requested',\n // Canonical geometry for a geometry-bearing detection: the consult behind\n // `ConsultAnchoredText` (SMELTER-OWNS-OCR P2). Its omission broke every\n // PDF detection job at the transport probe\n // (.plans/WORKER-ANCHORED-TEXT-CHANNEL.md); the census below now fails the\n // BUILD when this list and the declared awaits drift.\n 'browse:anchored-text-requested',\n // Durability acknowledgement for a unit's annotations (JOB-RESTART-SAFETY\n // P6). The worker AWAITS this one — a unit may not advance until its\n // annotations are in the event log — so its replies must be in the narrow\n // channel set or every commit fails fast with `bus.unsubscribed`.\n 'mark:commit',\n] as const satisfies readonly BusOperationKey[];\n\n/** The derived global SSE channel set for a worker's transport. */\nexport const WORKER_CHANNELS: readonly string[] = replyChannelsFor(WORKER_AWAITED_OPERATIONS);\n\n/**\n * The build-time census gate (WORKER-ANCHORED-TEXT-CHANNEL F2).\n *\n * `WORKER_AWAITED_OPERATIONS` restates a fact the code owns — which\n * operations worker paths call `busRequest` on — and one of those calls\n * hides behind an injected seam (`ConsultAnchoredText` → the SDK), where no\n * grep and no runtime probe-before-shipping can see it. So every awaiting\n * site DECLARES its operation next to the call (the `*Awaits` aliases), the\n * declarations are assembled here, and this constant compiles ONLY when the\n * list and the declarations agree in BOTH directions. Drift fails the build\n * with the drifted operation named in the type error — at build time, not at\n * the first live job. `busRequest`'s `bus.unsubscribed` probe remains the\n * runtime backstop for an await nobody declared.\n */\ntype DeclaredWorkerAwaits =\n | JobClaimAwaits // job-claim-adapter.ts — claiming an announced job\n | DescriptorReadAwaits // worker-process.ts — the resource descriptor read\n | MarkCommitAwaits // worker-process.ts — the durability ack (JOB-RESTART-SAFETY P6)\n | ConsultAnchoredTextAwaits; // prepare-detection.ts — canonical geometry (SMELTER-OWNS-OCR P2)\n\ntype WorkerAwaitCensusDrift =\n | Exclude<DeclaredWorkerAwaits, (typeof WORKER_AWAITED_OPERATIONS)[number]>\n | Exclude<(typeof WORKER_AWAITED_OPERATIONS)[number], DeclaredWorkerAwaits>;\n\nexport const workerAwaitCensus: [WorkerAwaitCensusDrift] extends [never]\n ? 'in-census'\n : WorkerAwaitCensusDrift = 'in-census';\n\nexport function parseGatewayUrl(url: string): { protocol: 'http' | 'https'; host: string; port: number } {\n const parsed = new URL(url);\n const protocol = (parsed.protocol.replace(':', '') === 'https' ? 'https' : 'http') as 'http' | 'https';\n const host = parsed.hostname;\n const port = parsed.port\n ? Number(parsed.port)\n : protocol === 'https' ? 443 : 80;\n return { protocol, host, port };\n}\n\n/**\n * Exchange the worker secret for this agent's JWT and its canonical DID.\n * The DID is minted by the gateway (from its `site.domain`) — the caller\n * carries it verbatim.\n *\n * Connection-level failures (`TypeError: fetch failed`) are retried with\n * exponential backoff: the gateway may be mid-restart or the container\n * network still warming up when this process starts, and orchestration\n * runs workers with `--rm` and no restart policy — exiting on the first\n * failed fetch is permanent death. HTTP-level rejections (401 on a bad\n * secret) are NOT retried; the gateway is up and said no.\n */\nexport async function authenticateAgent(opts: {\n gatewayBaseUrl: string;\n workerSecret: string;\n provider: string;\n model: string;\n logger?: Logger;\n retry?: RetryPolicy;\n}): Promise<{ token: string; did: string }> {\n const { gatewayBaseUrl, workerSecret, provider, model, logger, retry = STARTUP_FETCH_RETRY } = opts;\n if (!workerSecret) {\n throw new Error('SEMIONT_WORKER_SECRET is required to authenticate worker agents');\n }\n\n return retryWithBackoff(\n async () => {\n const response = await fetch(`${gatewayBaseUrl}/api/tokens/agent`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ secret: workerSecret, provider, model }),\n });\n\n if (!response.ok) {\n throw new Error(`Agent authentication failed for ${provider}:${model}: ${response.status} ${response.statusText}`);\n }\n\n return await response.json() as { token: string; did: string };\n },\n isTransientFetchError,\n retry,\n ({ attempt, attempts, delayMs, error }) => {\n logger?.warn('Gateway unreachable, retrying agent authentication', {\n agent: `${provider}:${model}`,\n attempt,\n attempts,\n retryInMs: delayMs,\n error: error instanceof Error ? error.message : String(error),\n });\n },\n );\n}\n\nexport async function startAgentWorker(\n opts: WorkerRuntimeOptions,\n): Promise<AgentWorkerHandle> {\n const { group, gatewayBaseUrl, workerSecret, contentReads, logger } = opts;\n const { inference } = group;\n\n const { protocol, host, port } = parseGatewayUrl(gatewayBaseUrl);\n const { token: initialToken, did } = await authenticateAgent({\n gatewayBaseUrl,\n workerSecret,\n provider: inference.type,\n model: inference.model,\n logger,\n });\n\n // The exchange minted this worker's canonical DID (from the gateway's\n // site.domain) and we carry it VERBATIM — never re-derive identity from\n // the URL we happen to dial (`host` is connection topology only). One\n // logical agent previously got two DIDs this way:\n // .plans/bugs/agent-did-host-skew.md.\n const generator: Agent = didToAgent(did);\n\n const kbId = `agent-${inference.type}-${inference.model}-${hostname()}`;\n const endpoint: HttpEndpoint = { kind: 'http', host, port, protocol };\n const kb: KbTarget = {\n id: kbId,\n label: `${inference.type} / ${inference.model} @ ${host}`,\n email: `agent@${host}`,\n endpoint,\n };\n const storage = new InMemorySessionStorage();\n setStoredSession(storage, kbId, { access: initialToken, refresh: '' });\n\n const token$ = new BehaviorSubject<AccessToken | null>(null);\n let session!: SemiontSession;\n const transport = new HttpTransport({\n baseUrl: baseUrl(kbGatewayUrl(endpoint)),\n token$,\n tokenRefresher: () => session.refresh().then((t) => t ?? null),\n // Only the reply channels this process awaits — not the full bridged\n // set. See WORKER_AWAITED_OPERATIONS.\n channels: WORKER_CHANNELS,\n });\n const content = new HttpContentTransport(transport);\n const client = new SemiontClient(transport, content, transport);\n session = new SemiontSession({\n kb,\n storage,\n client,\n token$,\n refresh: async () => {\n try {\n const { token } = await authenticateAgent({\n gatewayBaseUrl,\n workerSecret,\n provider: inference.type,\n model: inference.model,\n logger,\n });\n return token;\n } catch (err) {\n logger.error('Agent token refresh failed', {\n error: err instanceof Error ? err.message : String(err),\n agent: did,\n });\n return null;\n }\n },\n onError: (err) => {\n logger.error('Session error', { code: err.code, message: err.message, agent: did });\n },\n });\n await session.ready;\n\n const adapter = startWorkerProcess({\n session,\n jobTypes: group.jobTypes,\n inferenceClient: group.client,\n generator,\n // Byte reads for decode-path media only. A geometry-bearing type's text\n // never comes from bytes here — it is CONSULTED from the Smelter's\n // canonical anchored text over the bus (SMELTER-OWNS-OCR P2), and this\n // worker cannot derive even by mistake (READ-VS-EXTRACT P2).\n contentReads,\n logger,\n });\n\n logger.info('Agent ready', {\n did,\n provider: inference.type,\n model: inference.model,\n jobTypes: group.jobTypes,\n });\n\n return {\n session,\n vitals: () => ({\n provider: inference.type,\n model: inference.model,\n did,\n jobTypes: group.jobTypes,\n ...adapter.vitals(),\n }),\n dispose: async () => {\n adapter.dispose();\n await session.dispose();\n },\n };\n}\n"]}