/** * EvaluatorRunner — Peer runner for principle evaluation (PRI-67). * * Migrated to extend BasePeerRunner (PRI-302). The shared lease → buildContext → * invoke → poll → fetch → validate → succeed/fail pipeline is now in the base * class. This file only contains Evaluator-specific logic. * * Key business semantics: * - Evaluator approved → must validate the principle-bearing Scribe artifact, * NOT the Artificer plan artifact. This is the critical lineage contract. * - resolvePrincipleBearerArtifact uses sourceTrace.scribeArtifactId first, * then falls back to lineage search. Ambiguous candidates → fail loud. * - updateValidationStatus returning false → structured telemetry, no silent skip. * * ERR considerations: * - ERR-001 / ERR-005: output is `unknown` until validateOutput passes. * - ERR-004 / ERR-008: sourceTrace / scribeArtifactId / sourceArtificerArtifactId * must be internally consistent. * - ERR-018 / ERR-019: validationStatus update target must be the correct * principle-bearing artifact, never stale or wrong. * - ERR-025: tests must exercise real evaluator runner path. * - ERR-048: activation write/read path must not break. * * @see docs/adr/0003-peer-agent-state-machine-orchestration.md * @see BasePeerRunner in runner/base-peer-runner.ts */ import type { RunHandle } from '../runtime-protocol.js'; import type { EvaluatorOutputV1, EvaluatorValidator } from './evaluator-output.js'; import type { TaskRecord } from '../task-status.js'; import { type PDErrorCategory } from '../error-categories.js'; import { type RepairPayload } from './pitask-metadata.js'; import { type PreviousEvaluationContext, type HostToolCatalogFacts } from './evaluator-prompt-builder.js'; import type { FormationContext } from './formation-context.js'; import type { ArtificerHostSemanticContext } from './artificer-prompt-builder.js'; import { type InternalizationChannel, type PipelineTopologyMode, type ArtifactRef } from './peer-runner-contracts.js'; import { BasePeerRunner } from '../runner/base-peer-runner.js'; import type { PeerRunnerOptions, PeerRunnerDeps, PeerRunnerResult, PeerRunnerValidationResult } from '../runner/peer-runner-types.js'; import type { EffectivePdConfig } from '../config/pd-config-types.js'; import type { OutputLanguage } from '../language-directive.js'; import { type RefinerRuleHostGateDeps } from './refiner-rulehost-gate.js'; /** Context built by EvaluatorRunner.buildContext() and consumed by invokeRuntime(). */ interface EvaluatorContext { readonly contextHash: string; readonly artificerArtifact: string | null; readonly sourceArtificerArtifactId: string | null; /** * Scribe principle artifact contentJson (RuleHost MVP Activation, PRD Decision 12). * Loaded so the evaluator LLM can judge code intentConsistency/scopePrecision * against the original principle text. Null when no scribe artifact is resolvable * (scribeArtifactId missing/malformed or upstream artifact unavailable). */ readonly scribeArtifact: string | null; readonly sourceScribeArtifactId: string | null; /** * PRI-630 收敛契约: 依赖 artificer 的 repairPayload (第 2+ 轮存在)。 * 派生 previousEvaluation 注入 prompt — 上轮 decision/score/concerns/ * requiredChanges(稳定 id)/repairIteration。 */ readonly dependencyRepairPayload?: RepairPayload; /** PRI-630: 由 dependencyRepairPayload 解析的上轮评估上下文 (首轮 undefined) */ readonly previousEvaluation?: PreviousEvaluationContext; /** * PRI-843 (DC-4a): bounded formation-evidence projection (dreamer proposals + * source diagnosis + provenance) resolved from the scribe artifact's * authoritative `sourceTrace.dreamerArtifactId`. Undefined when the lineage id * is absent or the formation cannot be resolved — the prompt then keeps its * pre-PRI-843 shape exactly (legacy / degraded compatibility). */ readonly formationContext?: FormationContext; } export type EvaluatorRunnerResultStatus = 'succeeded' | 'failed' | 'retried'; export interface EvaluatorRunnerResult { readonly status: EvaluatorRunnerResultStatus; readonly taskId: string; readonly runId?: string; readonly artifactId?: string; readonly resultRef?: string; readonly contextHash?: string; readonly output?: EvaluatorOutputV1; readonly errorCategory?: PDErrorCategory; readonly failureReason?: string; readonly attemptCount: number; } /** * EvaluatorRunner options. Extends PeerRunnerOptions with an optional * RuleHost sandbox gate deps (PRI-426). When `gateDeps` is provided AND the * evaluator output is V2 (code-bearing), succeedTask runs a single-round * adversarial sandbox replay and populates `adversarialResult`. * * When `gateDeps` is absent, V2 outputs still validate but no replay runs — * this preserves backward compatibility for callers not yet wired to the * sandbox (e.g. V1-only test fixtures, pre-Phase-6 assembly). */ export interface EvaluatorRunnerOptions extends PeerRunnerOptions { readonly gateDeps?: RefinerRuleHostGateDeps; /** * PRI-630 工具目录权威: runtime-authoritative host tool facts (readOnly / * write 工具名)。由宿主装配层注入;缺失时 prompt 声明 degraded 规则 — * 工具名差异不得成为 hard blocker。 */ readonly hostToolCatalog?: HostToolCatalogFacts; /** * PRI-741: host semantic projection (real host tool names + kinds from the * SAME registry provenance as gateDeps). Used ONLY to generate the * host-name parity replay case — the prompt itself is unchanged. Undefined = * no host-alias case is generated (observable skip event). */ readonly hostSemanticContext?: ArtificerHostSemanticContext; /** * PR B (ADR-0019 pattern, mirrors ArtificerRunner): effective config for * feature flag resolution (e.g. the repair-loop and degradation flags). */ readonly effectiveConfig?: EffectivePdConfig; } export interface ResolvedEvaluatorRunnerOptions { readonly pollIntervalMs: number; readonly timeoutMs: number; readonly defaultMaxAttempts: number; readonly owner: string; readonly runtimeKind: string; readonly agentId: string; /** * Owner's preferred language for review fields (PRI-714). Forwarded to * EvaluatorPromptBuilder so summary/concerns/requiredChanges/codeReview * explanations follow the owner's language. Undefined = no directive * (backward compatible). */ readonly outputLanguage?: OutputLanguage; } export declare const DEFAULT_EVALUATOR_RUNNER_OPTIONS: Readonly>; export declare function resolveEvaluatorRunnerOptions(options: EvaluatorRunnerOptions): ResolvedEvaluatorRunnerOptions; /** * PRI-509: Parameters for seeding an artificer repair task. * * The evaluator runner constructs the repairPayload (core logic — 6 fields * sourced from the current evaluator output) and resolves inherited lineage * from the dependency artificer task. The actual task record creation is * delegated to the plugin layer via `seedArtificerRepairTask` to preserve * the core/plugin boundary (core peer runners do NOT directly orchestrate * task creation — enforced by architecture-regression.test.ts). */ export interface SeedArtificerRepairParams { /** The repair payload (6 fields) constructed by the evaluator runner. */ readonly repairPayload: RepairPayload; /** Scribe task IDs inherited from the original artificer task. */ readonly inheritedDependencyTaskIds: readonly string[]; /** Channel inherited from the original artificer task. */ readonly inheritedChannel: InternalizationChannel; /** PRI-720: topology mode inherited from the original artificer task (full_chain override). */ readonly inheritedPipelineMode?: PipelineTopologyMode; /** Timeout inherited from the original artificer task. */ readonly inheritedTimeoutMs: number; /** Input artifact refs inherited from the original artificer task. */ readonly inheritedInputArtifactRefs: readonly ArtifactRef[]; } export interface EvaluatorRunnerDeps extends PeerRunnerDeps { readonly validator: EvaluatorValidator; /** * PRI-509: feature flag resolver for the evaluator→artificer repair loop. * When omitted or returns false, evaluator needs_revision follows the * legacy path (no repair task seeded). When returns true and the * decision is needs_revision, the evaluator seeds an artificer repair * task (up to 2 rounds) or marks the task needs_human_review on the * 3rd round (fail loud, EP-03). * * Injected by the plugin layer (which reads EffectivePdConfig); core * stays pure logic with no direct config coupling (D5). */ readonly isRepairLoopEnabled?: () => boolean; /** * PRI-509: Seeder function for artificer repair tasks. * * The plugin layer implements this by serializing the repairPayload + * inherited metadata into diagnosticJson and calling stateManager * task-creation. Core peer runners must NOT call task-creation directly * (architecture-regression.test.ts enforces this boundary). * * Returns the newly created repair task's ID. */ readonly seedArtificerRepairTask?: (params: SeedArtificerRepairParams) => Promise; } export declare class EvaluatorRunner extends BasePeerRunner { private readonly validator; /** * Optional RuleHost sandbox gate deps (PRI-426). When present and the output * is V2, succeedTask runs a single adversarial sandbox replay. Absent = no * replay (backward compatible). */ private readonly gateDeps; /** * PRI-509: feature flag resolver for the evaluator→artificer repair loop. * Null when the deps did not inject the resolver (= disabled, legacy path). */ private readonly repairLoopEnabledResolver; /** * PRI-509: seeder function for artificer repair tasks. * Null when the deps did not inject the seeder (= repair seeding unavailable). */ private readonly repairTaskSeeder; /** PRI-630: runtime-authoritative tool facts; null = catalog unavailable (degraded rule in prompt) */ private readonly hostToolCatalog; /** PRI-741: host semantic projection for the host-name parity replay case; null = skipped. */ private readonly hostSemanticContext; /** * PRI-714: owner's preferred language for review fields, forwarded into * the evaluator prompt (language directive on summary/concerns/…). * Undefined = no directive (backward compatible). */ private readonly outputLanguage; constructor(deps: EvaluatorRunnerDeps, options: EvaluatorRunnerOptions); /** * Returns true iff the evaluator→artificer repair loop feature flag is on. */ private isRepairLoopEnabled; get permanentErrorCategories(): ReadonlySet; buildContext(taskId: string): Promise; invokeRuntime(taskId: string, context: EvaluatorContext): Promise; /** * PRI-843: narrow task view for formation-context resolution (the scribe * adapter, verbatim): phase identity lives on the task row, never on the * artifact, so the diagnostic predecessor can only be found via task lookup. */ private lookupFormationTask; /** * PRI-630 收敛契约 (SPEC §18.1): 解析上轮评估上下文 — 从 dependency * artificer 的 repairPayload.sourceEvaluatorTaskId 找到上轮 evaluator, * 读取其最近 principle artifact,按 rc-1/rc-2 守卫解析 evaluation 字段。 * requirements 用稳定 id (req-1..N, 上轮 requiredChanges 顺序)。 * 解析失败 → 结构化降级事件 + undefined (保持既有行为,可观测)。 */ private resolvePreviousEvaluation; /** * Build the evaluator prompt from the full predecessor artifacts. */ private buildEvaluatorPrompt; validateOutput(output: unknown, taskId: string, context: EvaluatorContext): Promise; succeedTask(taskId: string, runId: string, output: EvaluatorOutputV1, task: TaskRecord, contextHash: string, context: EvaluatorContext): Promise>; /** * P0 (verdict drift): 执行 decision 的治理效果 (fresh 与 resume 共用, * 幂等): approved → validate principle bearer;needs_revision (repair loop * on) → deterministic repair seed / max-iterations needs_human_review; * rejected → 无效果。 * * 返回非 null = terminal 结果 (needs_human_review 族,caller 直接返回, * 不得 markTaskSucceeded);null = effects 完成,caller 标 intent applied * 后 markSucceeded。 */ private applyEvaluatorDecisionEffects; /** * PRI-703 Phase 2: extract failed replay caseIds from the evaluator output. * Trust-boundary (rc-1/rc-4): adversarialResult.failedCases is untrusted * artifact content — validate each element's caseId shape; malformed entries * are skipped (they cannot inform scoping decisions). */ private static extractFailedCases; /** * PRI-703 Phase 2: resolve the rule's requiresContextVersion from the * DURABLE artificer artifact (never from the LLM-forgible evaluator copy). * Three-way result (see resolveRequiresContextVersionFromArtifact): * 2 / undefined → resolved (v2 / v1) — the scope partition MUST run; * null → unresolvable (attribution stays inconclusive — fail-open to the * existing repair path, never blocks the loop on a read error). * * 评审 P1 修正:key-absent on a PARSED artifact is deterministically v1 * (artificer schema only ever writes literal 2) and must reach the * partition — collapsing it into null made the out-of-scope routing * unreachable in production for exactly its target population (v1 rules). */ private resolveRequiresContextVersion; /** * PRI-509: Resolve the prior repair iteration by reading the dependency * artificer task's repairPayload (rc-7: written at task creation, never * inferred at read). Returns 0 when the dependency artificer has no * repairPayload (Round-1 evaluator → first repair). * * Loop state freshness (EP-05, ERR-015/018/019): each evaluator round reads * the CURRENT dependency artificer's repairPayload — never a cached value. * * PRI-718: the dep-derived value alone is NOT the durable truth — the dep * chain can be stale (e.g. an older repair's recovered transition re-pointed * the evaluator at a superseded artifact). The iteration budget must reflect * every durable repair round for THIS evaluator, so the result is the max of * the dep-derived value and the highest existing deterministic repair id * (probe is bounded: the budget gate stops at 2; the cap only guards against * pathological id growth). */ private resolvePriorRepairIteration; /** * 把 runner verdict 持久化进任务 diagnosticJson(commit 门控的输入)。 * 失败不静默 (rc-9): emitEvent 后吞掉 — verdict 已在 events/runs 中可观测, * 且 commit 门对缺失 verdict 走 legacy 推进,不会因记录失败而卡链。 */ /** * P0 (INV-2): needs_human_review 是 completion effect 的 materialize 操作 — * fail-closed + read-back。写失败 throw → retry_wait → 入口门 resume 同一 * effect,不问 LLM;禁止吞错后让 caller 标 intent applied * (intent applied ⇔ 其 durable effect 已 materialize)。 */ private markNeedsHumanReviewOrThrow; /** * verdict + completion intent 原子落库 (单次 metadata 写)。intent 的存在 * 证明 output 已 durable (updateRunOutput 在 succeedTask 最前)。 * 同 epoch crash/retry 重跑经 maybeResumePendingIntent resume,不重问 LLM。 */ private recordCompletionOrThrow; /** intent APPLIED 后才允许 terminal (P0 invariant 5)。写失败 fail loud。 */ private markCompletionIntentAppliedOrThrow; /** * 入口恢复门 (BasePeerRunner hook): pending completion intent (同 epoch) * 是 recovery authority。返回非 null = 本次 run 以 resume 完成 (LLM 未被 * 调用);返回 null = 走正常 LLM 管线。真正的 revision reopen 已清空 * intent (新 epoch 允许新 verdict);epoch 不匹配的残留视为 stale。 */ protected maybeResumePendingIntent(taskId: string, leasedTask: TaskRecord): Promise | null>; /** * P0 (INV-1/INV-5): applied intent 的补 terminal — effects 已 materialize * (applied ⇒ INV-2 保证),仅 markTaskSucceeded 缺失。不调用 LLM。 */ private finalizeAppliedIntentTerminal; /** * PRI-629: 应用 Owner verdict override 并收敛 terminal。 * * 顺序 (SPEC §10/§30): 恢复 durable output → 幂等效果 (override decision) * → completion intent 标 applied → resolution 标 applied → markTaskSucceeded。 * 任何 crash 窗口重放同一 resolution,不重新调用 LLM。机器 verdict * (runnerDecision) 永不改写。 */ private applyOwnerVerdictOverrideAndFinalize; /** * 从 runs 表恢复 intent 落库前已持久化的 validated output,并交叉核对 * decision 与 intent 一致 (authority 记录一致性)。intent 的存在保证 * updateRunOutput 曾成功;缺失/损坏/漂移 = 存储腐坏 → fail loud。 */ private recoverIntentOutput; /** * PRI-509: Seed an artificer repair task or mark the evaluator task * needs_human_review when max iterations (2) are reached. * * Returns: * - { kind: 'repair_seeded', taskId } — a new artificer repair task was created. * - { kind: 'max_iterations_reached' } — task marked needs_human_review (fail loud). * * Trust boundary (rc-1, rc-2): evaluator output is already validated by * validateOutput before succeedTask is called. requiredChanges / concerns * are typed as readonly string[] on EvaluatorEvaluation, so element-level * re-validation is not required here (rc-4 N/A — not unknown at this point). */ /** * Round-2 R2 (Owner 指令 2026-09-08): 从 durable 证据派生本 verdict 的治理 * 效果。fresh 与 resume 调用同一派生 —— 输入全部是 durable 事实: * - output: intent 落库前已由 executeDeterministicReplay 持久化的 * adversarialResult(resume 时 recoverIntentOutput 从 run.outputPayload * 恢复同一对象); * - sourceArtificerArtifactId → durable 工件的 requiresContextVersion; * - priorRepairIteration → durable dependency repairPayload。 * record 只持久化本函数的结论,不做判断 (指令要求: 禁止把复杂判断逻辑 * 塞进 recordCompletionOrThrow)。返回 undefined = governance_transition * (正常效果由 applyEvaluatorDecisionEffects 执行,无需特判)。 * * fresh 路径的 diagnosticReplayEvidence 仅用于验证"重放确实执行过"这一 * 控制流事实 (P1 provenance); resume 路径不传时, 以 durable adversarialResult * 的形态 (passed === false 且 failedCases 非空) 为等价判据 —— intent 存在 * 本身即证明 fresh 侧重放已执行并持久化。 */ /** * Round-2 R2: 读取当前 durable completion intent (pending 或 applied 均可读 — * applied 意味着效果已 materialize, 读侧只用于一致性核对, 不会改变路由)。 * 解析失败/缺 intent 返回 null — 出界判定回退到 fresh 快路径判定 * (fail-closed, 绝不因读不到 intent 而静默 seed)。 */ private readPendingOrAppliedCompletionIntent; private deriveGovernanceEffect; private maybeSeedArtificerRepair; /** * Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008). * Only fill when absent via Object.hasOwn — present-but-falsy values * must reach validation and fail loud (Runtime Contract Rule 3). * * generatedAt override is handled by the base class — subclasses must call * super.postFetchTransform() to inherit it. */ protected postFetchTransform(taskId: string, untrustedOutput: unknown, _context: EvaluatorContext): void; protected emitSuccessTelemetry(taskId: string, output: EvaluatorOutputV1): void; /** * Check lineage strip contract after validation passes. * Validates sourceTrace.scribeArtifactId consistency (ERR-004, ERR-008). */ protected checkLineageIntegrity(taskId: string, output: EvaluatorOutputV1, _context: EvaluatorContext): void; /** * Run a single-round adversarial sandbox replay (PRD Decision 11d). * PRI-634 A2/R1: the `output` is accepted in V1 shape too — a code-bearing * Artificer artifact requires the gate regardless of whether the evaluator * LLM emitted optional V2 fields. Only V2-shaped outputs can contribute * LLM-supplied adversarialCases (checked via isEvaluatorOutputV2 below). * Pure orchestration of pure functions: * 1. Skip if passive review failed (decision !== 'approved' is the LLM's * short-circuit signal — no code to defend). Now observable (R9). * 2. Convert adversarialCases → GoldenTrace (all negative, PRI-423). * 3. Merge ≥1 positive case from the Artificer golden trace. If the * artificer artifact has no goldenTraceCases (V1 mismatch), degrade: * skip replay with telemetry — do NOT crash. * 4. Invoke evaluateRefinerRuleHostGate via injected gateDeps. * 5. Populate adversarialResult from the gate result. * * Never throws — all failure modes degrade to a returned result with a * structured reason (ERR-018). The caller persists the updated output. */ private runAdversarialReplay; /** * PRI-634 R4: verdict-agnostic deterministic replay core. Runs the sandbox * gate against the Artificer implementation code and returns the updated * output carrying `adversarialResult`. Unlike runAdversarialReplay this does * NOT short-circuit on decision !== 'approved' — the caller decides when a * replay is useful (approved → binding; needs_revision → diagnostic * evidence). Never throws — all failure modes degrade to a returned result * with a structured reason (ERR-018). The caller persists the updated output. */ private executeDeterministicReplay; /** * Parse the Artificer artifact contentJson defensively (Runtime Contract * Rule 1/2/5). Returns null on any structural issue — the caller degrades. */ private parseArtificerArtifact; /** * Extract a principle ID from a PIArtifactRecord. Mirrors the logic in * activation/low-risk-writers.ts extractPrincipleId() but operates on * PIArtifactRecord (internalization module type) instead of PIArtifactSnapshot * (activation module type). Kept inline to avoid a cross-module runtime * dependency on the activation module. * * Resolution order: * 1. record.sourcePrincipleId (top-level field) * 2. parsed.principleId (contentJson) * 3. parsed.sourcePrincipleId (contentJson) * 4. parsed.principleDraft.title (contentJson — scribe output shape) */ private static extractPrincipleIdFromArtifact; /** * Extract structurally-valid positive GoldenTraceCases from the Artificer * goldenTraceCases array. Uses buildGoldenTraceFromArtificer (which re- * validates each case) and filters for kind='positive'. Returns [] when * the input is missing/malformed — the caller degrades to a skip (ERR-069: * never trust unvalidated candidates). */ private extractPositiveCases; /** * PRI-741: host-name parity case. The author's golden trace uses their own * tool-name vocabulary (often generic LLM baseline names); the rule must * reach the SAME decision when the real host dispatches one of ITS names * with the same canonicalKind. The base case is the first NEGATIVE (block) * case — a rule matching by author tool-NAME equality silently never fires * under the host's real name, which shows up exactly as a missed block; * an allow-expectation variant would pass trivially. The host name comes * from the host semantic projection (options.hostSemanticContext — the same * registry provenance as gateDeps); the author kind comes from the core * baseline canonicalizer, so no second semantic truth is introduced. * * Returns null when no variant applies. All skips EXCEPT one are observable * (skip event with structured reason + nextAction, rc-9): a host projection * that is absent/empty, golden cases that are missing or structurally * invalid, a base case without a usable tool name, and no host tool sharing * the author's kind all emit `host_alias_case_skipped`. When the author's * name is ALREADY host-real the variant is redundant (the replay already * exercises the host surface), so that path returns null silently. */ private generateHostAliasCase; /** * PRI-485 Phase 6: derive the v2 adversarial case spec from the Artificer * artifact and generate the 5 canonical v2 cases. * * Spec derivation: * - toolName: the first entry in `affectedTools` (validated as a non-empty * string array). Falls back to the first positive case's toolName when * affectedTools is missing/malformed — the rule still governs that tool. * - targetPath: the first positive case's `params.path` (validated as a * non-empty string). When absent, degrade with telemetry (rc-9) and * return [] — v2 cases cannot be path-realistic without a target path. * - canonicalKind: canonicalizeToolKind(toolName) — pure lookup. * * Never throws. All malformed inputs degrade to [] with a telemetry event * carrying a structured `reason` + `nextAction` (Runtime Contract Rule 9). */ private generateV2CasesFromArtificer; /** * Map sandbox failed cases to EvaluatorAdversarialResult.failedCases. * * The sandbox reports failedCases by caseId; we enrich each with the * adversarial case's attackType and expectedDecision. Cases not found in * the adversarial set (e.g. the merged positive case failed — which would * indicate a code bug, not an adversarial failure) are reported with * attackType='boundary' as a safe default and a note in the rationale * (Runtime Contract Rule 9: graceful degradation includes a reason). */ private mapFailedCases; /** * Assemble and persist the rule artifact when adversarial replay passed * (PRD Decision 5). The rule artifact carries implementationCode + the * Artificer full golden trace + ruleHostGateDecision, with artifactKind='rule'. * After a successful write, marks the artifact 'validated' so RuleHostWriter * can activate it. * * Returns the rule artifactId on success, null on any degradation (missing * code/trace, write failure, validation-update failure). Every null path * emits structured telemetry with a reason (Runtime Rule 9, ERR-018). */ private assembleRuleArtifact; /** * Resolve the principle-bearing artifact that the evaluator should validate. * * Strategy 1: Use scribeArtifactId from sourceTrace (the Scribe artifact * carries principleDraft). * Strategy 2: Search lineage for principle-kind artifacts with principleDraft. * Strategy 3: No principle-bearing artifact found → telemetry, return null. * * Ambiguous candidates (more than 1) → fail loud with telemetry, return null. * Never silently pick the first candidate (ERR-018, ERR-019). */ private resolvePrincipleBearerArtifact; /** * Check if an artifact's contentJson contains principle-bearing content. * Uses Object.hasOwn (ERR-013) and runtime type checks (ERR-001, ERR-005). */ private hasPrincipleDraftContent; private static isRecord; /** * Resolve principle-bearing artifacts from transitive lineage (depth 2). * * For each direct-lineage artifact, resolve its source task's dependencies * and search those artifacts for principle-kind artifacts with principleDraft * content. This handles the common case where the evaluator's direct * dependency is the artificer, and the scribe (principle-bearer) is a * transitive dependency (evaluator → artificer → scribe). * * Bounded to depth 2 to prevent unbounded traversal. Cycle-safe via the * visited set. */ private resolveTransitivePrincipleCandidates; } export {}; //# sourceMappingURL=evaluator-runner.d.ts.map