/** * PITaskMetadata — Persistence & Hydration (PRI-65) * * Provides the core-owned serialization/hydration contract for PITaskRecord metadata. * * Problem: PITaskRecord extends TaskRecord with internalization fields * (dependencyTaskIds, channel, timeoutMs, inputArtifactRefs, outputArtifactRefs), * but SqliteTaskStore only persists base TaskRecord fields + diagnostic_json. * There is no second task store — PI metadata must travel inside diagnosticJson. * * Solution: Store PI metadata in diagnosticJson using a namespaced JSON envelope. * This module provides: * - serializePITaskMetadata (PITaskMetadata → JSON string for diagnosticJson) * - parsePITaskMetadata (JSON string → PITaskMetadata | null, fail closed) * - hydratePITaskRecord (TaskRecord from store → PITaskRecord | null, fail closed) * - createPITaskDiagnosticJson (alias for serialize, explicit name for adapter use) * * Design principles: * - All functions are pure / total — no exceptions thrown * - Fail closed: invalid/missing data → null, not error * - Optional fields (parentTaskId, correlationId) must be non-empty string if present * - Namespaced key avoids collision with other diagnosticJson uses * * @see ADR-0003 Section 3.4 * @see docs/adr/0003-peer-agent-state-machine-orchestration.md */ import type { TaskRecord } from '../task-status.js'; import type { PITaskRecord, InternalizationChannel, PipelineTopologyMode, ArtifactRef } from './peer-runner-contracts.js'; import type { BoundEvidenceManifestV1 } from './owner-decision-review.js'; /** Namespace key used inside diagnosticJson to isolate PI metadata. */ export declare const PI_METADATA_KEY: 'pi_metadata'; /** * Evaluator repair payload (PRI-509). * * Carries the evaluator's structured feedback when decision === 'needs_revision' * so the seeded artificer repair task can address each required change instead * of regenerating blind. The metadata layer treats this as opaque; the * ArtificerRunner.buildContext reads it and constructs a pre-formatted * repairFeedback string for the prompt builder. * * Lineage (rc-6): * - sourceArtificerArtifactId: the prior artificer artifact that was rejected * - sourceEvaluatorTaskId: the evaluator task that returned needs_revision * * Loop state freshness (rc-7, EP-05, ERR-015/018/019): * - repairIteration is written at task creation time, never inferred at read. * - Round 1 = first repair (after initial evaluator needs_revision). * - Round 2 = second repair (after first repair's evaluator needs_revision). * - Max 2 rounds; a 3rd needs_revision fails loud via needs_human_review (EP-03). */ export interface RepairPayload { readonly requiredChanges: readonly string[]; readonly concerns: readonly string[]; readonly previousScore: number; readonly repairIteration: number; readonly sourceArtificerArtifactId: string; readonly sourceEvaluatorTaskId: string; /** * PRI-634 R4: 诊断性对抗重放 evidence(code-bearing + needs_revision 时 * evaluator 已执行 deterministic replay)。replay 通过 ≠ 语义正确,因此 * 不改变 verdict;但作为客观证据流入修复轮,下一轮 Artificer 可见 * (formatRepairFeedback 渲染)。缺失 = 该轮未执行 replay(e.g. 非 * code-bearing / gateDeps 未装配 / replay 未产出 adversarialResult)。 */ readonly diagnosticReplay?: { readonly ran: boolean; readonly passed: boolean; readonly failedCaseCount: number; }; /** * PRI-758: the evaluator run that seeded this repair. Crash-resume * idempotency: a resume of the SAME evaluator run re-enters the seed * path; when an existing repair task already carries this runId the * seeder reuses it instead of incrementing the iteration (which would * double-seed two repair tasks for one verdict). */ readonly sourceEvaluatorRunId?: string; /** * PRI-705 / PRI-703 Phase 2 (Owner 决策 2026-09-07): 修复轮失败归因 — * "哪里失败 / 为什么 / 下一步改哪里" 随载荷流动,修复 LLM 不再盲猜。 * 仅在确定归因可得时携带 (evaluator 在 seed 时由确定性分类器填入): * - attribution: 失败归属于哪一层 (FailureAttribution 词表) * - outOfScopeCaseIds: 被判定为 test-out-of-scope 的 v2-context case * (v1 通道结构性不可表达 — 修复轮不得尝试满足它们;纯 out-of-scope * 场景根本不会 seed 修复任务,本字段只在混合场景出现) * - reason: 人类可读归因摘要 (有界) * 全部可选 — 旧载荷/未分类失败不受影响 (rc-9: 缺失不降级语义)。 */ readonly failureAttribution?: { readonly attribution: string; readonly outOfScopeCaseIds?: readonly string[]; readonly reason: string; }; } /** * PRI-718: the deterministic artificer-repair task id convention — * `artificer-repair--r`. * * Single owner of the convention (P4): the two production seeders * (host-runtime consumer governance, pd-cli rulehost pipeline runner) and * the evaluator's durable-iteration probe all derive ids from here, so the * writer and the reader can never drift apart. */ export declare function artificerRepairTaskId(sourceEvaluatorTaskId: string, repairIteration: number): string; /** * 人工裁决上下文 (PRI-629): 任务进入 needs_human_review 时与 status 同一次 * task-row mutation 原子落库的"为什么找人"事实。classification (owner_decision * vs recovery) 由此派生;缺失 = legacy 任务,由 collectOwnerDecisionFacts 按 * durable facts 推断,模糊 → recovery (fail closed)。 */ export interface HumanReviewContext { /** 结构化原因码 (HumanReviewReasonCode;解析接受任意非空串以保持前向兼容) */ readonly reasonCode: string; /** 产出待裁决 verdict 的 run */ readonly sourceRunId: string; /** 该 run 的决策输出 artifact (evaluator/rollout 输出 artifact id) */ readonly sourceArtifactId?: string; /** sourceArtifactId 内容的 sha256 hex — Owner decision 的 stale 防护输入 */ readonly sourceArtifactHash?: string; /** 进入 needs_human_review 时的 revisionEpoch (= 当时 revisionCount) */ readonly revisionEpoch: number; /** * PRI-634: 规范 reasonCode 之外的可诊断细节(面向 Owner 展示)。 * 例:dispatch 被拒的具体 outcome.reason、候选解析被内容契约筛掉的产物清单。 * 不参与 buildOwnerReviewKey —— 后者只绑定 7 个稳定事实,新增本字段不影响 * 已有 pending resolution 的匹配。 */ readonly detail?: string; readonly createdAt: string; } /** * Owner 裁决动作 (PRI-629)。accept_current/reject_current 是 verdict override * (写 effectiveDecision,不改 runnerDecision);revise_once 授权一个额外 * revision epoch (不开自动预算,repairIteration 不变)。 */ export type OwnerResolutionAction = 'accept_current' | 'revise_once' | 'reject_current'; export type OwnerResolutionStatus = 'pending' | 'applied'; /** * task-scoped Owner authority log (PRI-629)。不是第二状态源: 它只记录 Owner * 对某个人工裁决事实快照 (reviewKey) 的决定;effective decision 的唯一解析点 * 是 resolveEffectiveRunnerDecision (owner-review.ts)。append-only: 保留历史 * resolution,不覆盖;同一 reviewKey 至多一条 (CAS 写入保证)。 */ export interface OwnerResolutionRecord { readonly resolutionId: string; /** 绑定裁决事实快照的稳定 hash — POST 时服务端重读 durable facts 重算比对 */ readonly reviewKey: string; readonly action: OwnerResolutionAction; readonly status: OwnerResolutionStatus; /** 服务端 auth context 推导的身份 (console token / operator_legacy) */ readonly ownerId: string; readonly credentialId?: string; readonly decidedAt: string; readonly appliedAt?: string; readonly sourceRunId: string; readonly sourceArtifactId: string; readonly sourceArtifactHash: string; readonly revisionEpoch: number; /** 机器原始判定 — 永久保留 (INV-03 machine verdict immutability) */ readonly machineDecision: RunnerDecision; /** 仅 verdict override 动作有;revise_once 无 (新 epoch 由新 verdict 产生) */ readonly effectiveDecision?: RunnerDecision; /** 仅 revise_once: 被 reopen 的修订目标任务 */ readonly targetTaskId?: string; /** 仅 revise_once: reopen 使用的 epoch-aware causeId */ readonly targetRevisionCauseId?: string; /** 仅 revise_once: 有界、已消毒的短指导 (仅作 revision feedback) */ readonly ownerInstruction?: string; /** v1.2 evidence integrity receipt; absent only on pre-v1.2 legacy records. */ readonly evidenceDigest?: string; readonly evidenceManifest?: BoundEvidenceManifestV1; readonly evidenceAcknowledgement?: { readonly kind: 'partial_evidence'; readonly acknowledged: true; readonly note?: string; }; } /** * PRI-700 因子 B (Owner 决策 2026-09-07): 上一次 attempt 的 validator 拒绝 * 全文。由 base-peer-runner.handleValidationError 在每次 output-invalid 后 * 写入 diagnosticJson 顶层(与 pi_metadata 信封并列的 lastValidatorErrors * 键,best-effort 持久化),下一次 attempt 的 runner prompt 从中回喂—— * 修复 attempt N+1 不再与 attempt N 同 prompt 零新信息重试(18/18 死锁 * 的结构性断路)。 * * 生命周期:本 attempt 校验失败时被当次新错误覆盖(rc-7 loop state * freshness),成功时不清理(残留由读取侧 sourceAttemptCount 新鲜度判定 * 屏蔽——只回喂 sourceAttemptCount === 当前 attempt-1 的记录)。 */ export interface LastValidatorErrors { /** 持久化时间(ISO) */ readonly recordedAt: string; /** 本 attempt 校验失败的错误类别(PDErrorCategory) */ readonly errorCategory: string; /** * validator 拒绝错误(≥1 条)。持久化侧存全文;本(读取/回喂)侧有界: * ≤10 条、每条 ≤500 字符(rc-8,见 boundRefedErrors)。 */ readonly errors: readonly string[]; /** * 写入时的 attempt 序号(= 写入侧 task.attemptCount)。读取侧做跨 * attempt/跨进程新鲜度判定:仅当 === 当前 attempt-1 时回喂。评审 * P1:运行时错误不清除本记录,无来源标识时 attempt N+2 或重启后的 * 重试会复用 attempt N 的旧错误。 */ readonly sourceAttemptCount: number; } /** * PRI-700 因子 B(评审 P1): 回喂新鲜度判定——纯函数,供 runner 消费。 * 仅当记录来自紧邻的上一个 attempt(sourceAttemptCount === 当前 * attempt-1)且存在 lease 上下文时回喂;否则由调用方发 suppression * 事件(rc-9)。进程重启安全:判定只依赖持久化的 sourceAttemptCount * 与 lease 派生的 attempt 序号。 */ export declare function isFreshForNextAttempt(record: LastValidatorErrors, currentLeasedAttempt: number | undefined): boolean; /** * Trust-boundary guard (rc-1, rc-4): diagnosticJson 顶层 * lastValidatorErrors 是 untrusted runtime data。sourceAttemptCount 必填 * 且为非负整数——缺来源标识的 legacy 记录返回 null(宁可少回喂一次, * 不回喂来源不明的旧错误)。 */ export declare function parseLastValidatorErrors(diagnosticJson: string | null | undefined): LastValidatorErrors | null; /** * PI-specific metadata stored inside TaskRecord.diagnosticJson. * All fields must be present except parentTaskId and correlationId (optional). */ export interface PITaskMetadata { dependencyTaskIds: string[]; channel: InternalizationChannel; /** * PRI-720: explicit topology mode. New seeds always write it * ('standard' or 'full_chain') at seed time; ABSENT = a pre-PRI-720 record, * which keeps the legacy full-chain topology (AC12 — no reinterpretation). * Inherited unchanged by successor proposals. */ pipelineMode?: PipelineTopologyMode; timeoutMs: number; inputArtifactRefs: ArtifactRef[]; outputArtifactRefs: ArtifactRef[]; parentTaskId?: string; correlationId?: string; rejectionCount?: number; /** * Prior adversarial replay failures to inject into a Round-2+ Artificer * prompt (RuleHost MVP, PRI-428). Set by runAdversarialLoop when a prior * Evaluator round returned needs_revision. Treated as opaque text by * the metadata layer; the ArtificerRunner forwards it to the prompt builder. */ adversarialFeedback?: string; /** * Evaluator repair payload (PRI-509). Present only on artificer tasks seeded * by evaluator needs_revision. Carries the structured feedback * (requiredChanges/concerns/previousScore/repairIteration) so the artificer * can address each required change. Undefined on Round-1 artificer tasks. */ repairPayload?: RepairPayload; /** * Runner 判定(evaluator / rollout_reviewer 的 LLM 决策),由 runner 在 * succeedTask 收尾时写入。commitNextTaskProposal 依据它做单一迁移决策 * (MVP_CORE_LOOP_CONTRACT INV-02: needs_revision 不得同时 seed 正常后继)。 */ runnerDecision?: RunnerDecision; /** * 该任务被 revision reopen 的次数(每次 reopen +1)。revision 有界性的 * 一部分: 配合 rolloutRevisionPayload.revisionIteration / repairIteration * 构成 lineage 级 revision budget。 */ revisionCount?: number; /** * Rollout reviewer needs_revision 时注入到被 reopen 修订目标的反馈 * (scribe / artificer 的 prompt 侧注入,由各 runner buildContext 消费)。 */ revisionFeedback?: string; /** * P0-4 revision identity: 触发本次 reopen 的稳定 cause 标识 * (如 `repair-` / `rollout--r`)。 * reopenTaskForRevision 对相同 causeId 的重放是真正 no-op。 */ revisionCauseId?: string; /** * Rollout reviewer needs_revision 的修订路由载荷: 记录修订目标 stage、 * 迭代号与来源,保证 revision budget 可判定 (MVP_CORE_LOOP_CONTRACT INV-07)。 */ rolloutRevisionPayload?: RolloutRevisionPayload; /** * P0 (verdict drift): 一次 LLM verdict 的 durable completion intent。 * * 与 runnerDecision 在同一次 metadata 写入中落库 (原子): intent 存在且 * status='pending' ⇒ 该 verdict 的治理 transition 尚未完成。同一 * execution epoch 内 crash/retry/restart 重跑时,run() 入口必须 RESUME * 该 intent (跳过 LLM,幂等重放 effects),禁止让新的 LLM 输出覆盖它 * —— LLM 非确定性下,重问可能产生 approve/reject 漂移,与已发生的 * side effect (activation / repair seed / validation) 形成治理矛盾。 * * epoch 语义: revisionEpoch = 落库时的 revisionCount。真正的 revision * reopen 会递增 revisionCount 并清空本字段 (新 epoch 允许新 verdict); * epoch 不匹配的残留 intent 视为 stale,不得 resume。 */ completionIntent?: RunnerCompletionIntent; /** * PRI-629: 进入 needs_human_review 的结构化上下文 (与 status 原子同写)。 * classification / capability / reviewKey 的权威输入;缺失 = legacy。 */ humanReviewContext?: HumanReviewContext; /** * PRI-629: task-scoped Owner 裁决 log (append-only,同一 reviewKey 至多一条)。 * 不是状态源 — effective decision 由 resolveEffectiveRunnerDecision 唯一解析。 */ ownerResolutions?: readonly OwnerResolutionRecord[]; } /** evaluator / rollout_reviewer 的合法 runner 决策值 */ export type RunnerDecision = 'approved' | 'needs_revision' | 'rejected' | 'approve_rollout' | 'reject'; /** * 一次 verdict completion 的 durable intent (P0 verdict drift 修复)。 * effectPayload 语义按 decision 分派: * - needs_revision (rollout): revisionIteration = 本次 completion 的修订轮号 * (record 时由已 APPLIED 的 rolloutRevisionPayload 计出并锁定,resume * 据此继续同一轮,消除"applied 载荷属于上一轮还是本轮"的歧义); * - 其余 decision: 无 effect 载荷。 */ export interface RunnerCompletionIntent { readonly decision: RunnerDecision; /** 产出该 verdict 的 run — resume 时从其 outputPayload 恢复已验证输出 */ readonly sourceRunId: string; /** 落库时任务的 revisionCount — 同 epoch 才允许 resume */ readonly revisionEpoch: number; readonly status: 'pending' | 'applied'; readonly revisionIteration?: number; /** * P0-A (completion-intent 完整性): 该 completion 的效果类型。 * - 'governance_transition' (缺省): 正常治理 transition (dispatch / * revision routing / repair seed / validation),由 runner 的 effects 执行; * - 'needs_human_review': 终态人工裁决效果 (rollout budget exhausted 等) * — crash resume 必须继续该效果 (重写 needs_human_review),禁止重问 LLM。 */ readonly effect?: 'governance_transition' | 'needs_human_review'; /** * Round-2 R2 (Owner 指令 2026-09-08): deriveGovernanceEffect 从 durable 证据 * 派生的选定治理效果(与 P0-A 的 effect 同义;写侧统一写本键)。 * 'needs_human_review' ⇒ resume 必须恢复同一 NHR 效果(重写状态 + * reasonCode),禁止重新计算路由 / 重问 LLM / 再 seed repair。 * 缺失 = governance_transition(旧行为)。解析层 effect 与本键同读。 */ readonly selectedEffect?: 'needs_human_review'; /** * Round-2 R2: selectedEffect 的结构化原因码(evaluator_test_out_of_scope / * evaluator_repair_budget_exhausted / evaluator_repair_seed_failed)。 * resume 重放 NHR 时以本字段为准;缺失时兜底 evaluator_repair_seed_failed * (fail-closed 到 recovery 语义,绝不凭空升级为 decision-capable)。 */ readonly effectReasonCode?: string; } /** rollout needs_revision 的修订路由载荷 */ export interface RolloutRevisionPayload { readonly requiredChanges: readonly string[]; readonly revisionIteration: number; readonly sourceRolloutTaskId: string; readonly sourceArtifactId: string; readonly targetTaskKind: 'scribe' | 'artificer'; /** * B (最终复核) intent 状态机: * - 'pending': intent 已持久化,transition 尚未 materialize — crash/retry/ * restart 必须继续执行同一 iteration N,禁止自动 N+1; * - 'applied': reopen 已 materialize — 只有新的 needs_revision verdict 才 * 能 N→N+1;budget 按 APPLIED 计数,不按 intent 写入次数计。 * - undefined: 旧形状 (本状态机引入前) — 兼容视为 'applied'。 */ readonly status?: 'pending' | 'applied'; } /** * Serialize PITaskMetadata into a JSON string suitable for TaskRecord.diagnosticJson. * Uses a namespaced envelope: { "pi_metadata": { ... } } */ export declare function serializePITaskMetadata(metadata: PITaskMetadata): string; /** Alias for serializePITaskMetadata — explicit name for adapter/consumer use. */ export declare const createPITaskDiagnosticJson: typeof serializePITaskMetadata; /** * 从已 hydrate 的 PITaskRecord 重建可写 PITaskMetadata,浅覆盖指定字段。 * * 单一重建点 (DRY): evaluator/rollout 的 verdict 记录、修订路由记录、 * orchestrator 的 revision reopen 共用同一字段搬运逻辑——此前 4 处手写 * 逐字段 spread,新增字段时容易漏抄 (Phase 3.5 consolidation)。 * 注意: overrides 中显式 undefined 会覆盖为 undefined (serialize 时省略键), * 用于"清除旧 verdict"语义。 */ export declare function mergePITaskMetadata(base: PITaskRecord, overrides: Partial): PITaskMetadata; /** * Parse a diagnosticJson string into PITaskMetadata. * Returns null on any parse/validation failure (fail closed). * * Validation rules: * - Must be valid JSON * - Must contain pi_metadata key with all required fields * - channel must be a valid InternalizationChannel * - parentTaskId / correlationId if present must be non-empty strings */ export declare function parsePITaskMetadata(diagnosticJson: string): PITaskMetadata | null; /** * Hydrate a raw TaskRecord (as returned by SqliteTaskStore.getTask or listTasks) * into a PITaskRecord by reading and parsing its diagnosticJson. * * Fail-closed: returns null for any non-RunnerKind taskKind * even if diagnosticJson contains valid pi_metadata. This prevents the * InternalizationOrchestrator from treating a non-PI task as a PITaskRecord. * * Returns null if: * - taskKind is not a valid RunnerKind (PeerRunnerKind or DiagnosticianStageKind) * - diagnosticJson is missing or whitespace * - diagnosticJson is not valid JSON * - pi_metadata key is missing or invalid * - Any required PI field fails validation * - Optional field present but not a non-empty string */ export declare function hydratePITaskRecord(task: TaskRecord): PITaskRecord | null; //# sourceMappingURL=pitask-metadata.d.ts.map