diff --git a/amadeus/spaces/default/intents/260803-intent-autonomy/construction/quality-repair-runtime/code-generation/code-generation-plan.md b/amadeus/spaces/default/intents/260803-intent-autonomy/construction/quality-repair-runtime/code-generation/code-generation-plan.md new file mode 100644 index 000000000..7a4bbf006 --- /dev/null +++ b/amadeus/spaces/default/intents/260803-intent-autonomy/construction/quality-repair-runtime/code-generation/code-generation-plan.md @@ -0,0 +1,22 @@ +# Code Generation Plan — quality-repair-runtime + +## スコープと追跡元 + +U2 `quality-repair-runtime`(Issue #2096)のみを実装する。追跡元は USR-02 / USR-05 / USR-08、FR-QRP-001〜013、FR-LMC-010、FR-STP-003〜006、FR-HAR-001〜007、2096-AC01〜18、および Functional / NFR Design の QRP-A/E/P/R 規則である。テスト戦略は `Comprehensive` とし、決定論的 unit test、production coordinator / replay integration、5 harness projection、package / promote drift を同じ Bolt で閉じる。 + +U1 `loop-monitor-runtime` の generic Monitor、durable repository、Judge port、latch / resume contract を再利用する。quality の obligation / convergence / route 意味論を generic Monitor へ混入させず、harness 別 Core 分岐も作らない。 + +## 実装手順 + +- [x] **Step 1 — generic singleton route constraint seam**: U1 Monitor の compiled route 集合から delivery ごとの非空 subset constraint を検証・予約・Judge request へ伝播できるよう最小拡張する。quality 固有語彙は追加しない。(FR-QRP-009〜011、2096-AC09〜10) +- [x] **Step 2 — first-party contribution と activation**: trusted embedded contribution、descriptor exact resolution、`semi/full` 必須、`none` real-human opt-in / default-off、required outputs 初期空集合を pure contract として実装する。(USR-02 / USR-08、FR-QRP-001〜003 / 012、2096-AC01〜05) +- [x] **Step 3 — blocking evidence normalization**: reviewer / blocking sensor / required produce / verification・completion の closed observation を stable obligation、snapshot、resolved / added / retained、canonical fingerprint へ正規化する。advisory sensor と Request Changes は除外し、不完全 evidence は fail-closed にする。(FR-QRP-004〜008 / 013、2096-AC06〜08) +- [x] **Step 4 — bounded quality convergence**: T+1 window、initial / collecting / strict-progress / threshold、fixed-point / churn / regression / undetermined、replan-first と post-replan repair-stalled singleton constraint を pure reducer として実装する。(USR-05、FR-QRP-009〜011、2096-AC09〜11) +- [x] **Step 5 — durable production coordinator**: canonical quality event set、snapshot / progress / replan reservation-before-effect、closed attempt 0/1、local review cycle、generic Monitor delivery / Judge / latch、`REPAIR_STALLED` / suspended status を統合する。(FR-STP-003〜006、2096-AC10〜15) +- [x] **Step 6 — resume / replay / status**: `any-of[evidence-change,human-retry]`、atomic latch clear + new quality epoch + workflow unpark、same-fingerprint short circuit、cross-session replay と safe status envelope を実装する。(USR-05、FR-LMC-010、2096-AC11〜16) +- [x] **Step 7 — Comprehensive tests**: Step 1〜6 の red unit / integration testsを追加し、T-1 Judge 0、初回 T replan、strict progress reset、replan 後 T stalled、Request Changes 非変換、crash / replay / duplicate / cross-scope、latch / resume contract 再利用を検証する。(2096-AC01〜18) +- [x] **Step 8 — 5 harness projection と品質ゲート**: framework core から Claude Code / Codex / Cursor / OpenCode / Kimi Code へ生成し、共通 fixture の byte-equivalent contract、`bun run typecheck`、`bun run lint`、focused tests、`bun scripts/package.ts --check`、`bun run promote:self:check` を検証する。(FR-HAR-001〜007、2096-AC16〜18) + +## 非対象 + +U3〜U5、Intent grant、gate / question 認可、PR / merge、外部 runner / supervisor、Kiro live、外部 Plugin manifest、新 stage、固定総 retry cap、新規 mandatory artifact は実装しない。 diff --git a/amadeus/spaces/default/intents/260803-intent-autonomy/construction/quality-repair-runtime/code-generation/code-summary.md b/amadeus/spaces/default/intents/260803-intent-autonomy/construction/quality-repair-runtime/code-generation/code-summary.md new file mode 100644 index 000000000..47cb84e78 --- /dev/null +++ b/amadeus/spaces/default/intents/260803-intent-autonomy/construction/quality-repair-runtime/code-generation/code-summary.md @@ -0,0 +1,36 @@ +# Code Summary — quality-repair-runtime + +## 実装結果 + +U2 `quality-repair-runtime`(Issue #2096)を、U1 `loop-monitor-runtime` の durable delivery / Judge / latch / resume seam を再利用する first-party runtime として実装した。品質固有の obligation、T+1 convergence、replan-first、post-replan `repair-stalled` は専用 reducer に閉じ、generic Monitor には delivery 単位の非空 route subset constraint だけを追加した。 + +### Core + +- `amadeus-quality-repair.ts`: trusted embedded contribution、depth 別 activation、closed evidence normalization、stable obligation / snapshot / delta / fingerprint、T+1 convergence reducer、replan receipt / review-cycle identity を実装した。 +- `amadeus-quality-repair-runtime.ts`: quality event と generic Monitor event の atomic transaction、reservation-before-effect、closed attempt 0/1、reconcile-first、`REPAIR_STALLED`、suspended status、human/evidence resume、cross-session replay を実装した。 +- `amadeus-quality-repair-replay.ts`: canonical audit transaction の decode / repository / replay を実装した。 +- `amadeus-loop-monitor.ts` / `amadeus-loop-monitor-runtime.ts`: compiled route の検証済み subset を予約・Judge request・適用へ伝播し、replan target を post-replan interval の anchor に更新する generic seam を追加した。 +- audit event registry に `QUALITY_REPAIR_TRANSACTION_COMMITTED` を追加し、canonical payload の仕様と event count を同期した。 +- `docs/reference/12-state-machine.md` と coverage registry / ratchet を U2 の新規 canonical audit event へ同期した。 + +### 投影 + +同一 Core bytes を Claude Code、Codex、Cursor、OpenCode、Kimi Code の5ハーネスへ投影した。Kiro live、外部 runner / supervisor、U3〜U5、Intent grant、gate / question authorization は追加していない。 + +## テスト + +- pure unit: contribution / activation、blocking evidence、strict progress / fixed-point / churn / regression / undetermined、replan-first / repair-stalled、Request Changes 除外を検証した。 +- integration: T-1 Judge 0、初回 threshold replan、post-replan stalled、atomic resume、same-fingerprint no-op、cross-session replay、attempt 0/1 と attempt 2 不在、latch / resume contract 再利用、canonical serialization を検証した。 +- projection: 5ハーネスの quality runtime 3ファイルが Core と byte-identical であることを検証した。 +- 品質ゲート: `bun run typecheck`、`bun run lint`、最終 focused 128 tests / 2,185 expects、coverage registry freshness / ratchet、audit emitter / state-machine / audit-format drift、`bun scripts/package.ts --check`、`bun run promote:self:check`、`git diff --check` を通過した。lint は既存 baseline の cognitive-complexity warning のみで error はない。 +- `bun run test:ci` は全788ファイルを完走した。初回に U2 由来の event count、test size、audit taxonomy、coverage registry の同期漏れを検出し、すべて修正して上記 focused set で再検証した。古い Bolt base に由来する Pi conductor / package、complexity baseline 等の既存 drift は U2外として残した。 + +## 設計差分と除外 + +- 設計上の singleton route を generic runtime で安全に表現するため、U1 の full-set constraint を「compiled route の非空 subset」へ最小限に一般化した。quality 固有語彙は U1 Core へ追加していない。 +- U1 依存コミット `89d5afe35` はこの Bolt のローカル前提であり、U2コミットには含めない。 +- package / promote 実行で顕在化した、古い base に由来する `amadeus-harness-capability.ts`、`amadeus-harness.ts`、`amadeus-swarm.ts`、`amadeus-utility.ts`、Pi / Kiro 等の既存 drift は U2 の変更ではないため、U2コミット対象から明示的に除外する。 + +## 残作業 + +U2の実装残はない。後続は独立した Build and Test / 統合工程で扱う。 diff --git a/docs/reference/12-state-machine.md b/docs/reference/12-state-machine.md index 0bf3ca2ee..f40a02791 100644 --- a/docs/reference/12-state-machine.md +++ b/docs/reference/12-state-machine.md @@ -395,13 +395,14 @@ The four `SENSOR_*` events are emitted by the sensor dispatcher; `GUARDRAIL_LOAD | `RULE_LEARNED` | `tools/amadeus-learnings.ts` | The learning gate persisted a kept learning as a dated practice entry to `amadeus/spaces//memory/{project,team}.md` | | `SENSOR_PROPOSED` | `tools/amadeus-learnings.ts` | The learning gate scaffolded a project-tier sensor manifest and bound it to the originating stage's `sensors:` frontmatter | -### Loop monitor +### Loop monitor and quality repair -The Loop Monitor commits delivery observations, cycle triggers, Judge reservations and results, closed-route applications, and latch transitions as one canonical event set. The per-clone Replay Index is a repairable projection of this audit source of truth. +The Loop Monitor commits delivery observations, cycle triggers, Judge reservations and results, closed-route applications, and latch transitions as one canonical event set. The Quality Repair runtime commits each quality snapshot, progress, replan, stall, or resume transaction together with its generic Loop Monitor effects. The per-clone Replay Index is a repairable projection of these audit sources of truth. | Event | Emitter | Trigger | |---|---|---| | `LOOP_MONITOR_EVENT_SET_COMMITTED` | `tools/amadeus-loop-monitor-replay.ts` | One atomic Loop Monitor delivery, Judge, or latch transition committed | +| `QUALITY_REPAIR_TRANSACTION_COMMITTED` | `tools/amadeus-quality-repair-replay.ts` | One quality snapshot, progress, replan, stall, or resume transaction and its generic Monitor effects committed atomically | ### Swarm diff --git a/packages/framework/core/knowledge/amadeus-shared/audit-format.md b/packages/framework/core/knowledge/amadeus-shared/audit-format.md index 0d045f59a..c6a88c72b 100644 --- a/packages/framework/core/knowledge/amadeus-shared/audit-format.md +++ b/packages/framework/core/knowledge/amadeus-shared/audit-format.md @@ -29,7 +29,7 @@ tools read them from. Neither lists the record envelope — every record carries (the park pair, the practices events) show it in the table as the attribute it is. -## Event Registry (86 events, 21 categories) +## Event Registry (87 events, 21 categories) ### Workflow Lifecycle (7 events) @@ -243,13 +243,14 @@ Emitted by stage-protocol §13 (Learnings Ritual). The runtime-graph compile emi | `RULE_LEARNED` | The learning gate persisted a kept learning as a practice line under the routed heading in `{project,team}.md` | Stage, Candidate-ID, Destination, Heading, Source | — | `tools/amadeus-learnings.ts persist` | | `SENSOR_PROPOSED` | The learning gate scaffolded a project-tier sensor manifest and bound it to the originating stage's `sensors:` frontmatter | Stage, Candidate-ID, Sensor ID, Manifest path, Matches, Destinations, Source | — | `tools/amadeus-learnings.ts persist` | -### Loop Monitor (1 event) +### Loop Monitor and Quality Repair (2 events) The event set is the atomic canonical stream for delivery observation, cycle trigger, Judge reservation/result, closed route application, and latch transitions. The per-clone Replay Index is a repairable secondary projection and never replaces this audit source of truth. | Event | When | Required | Optional | Emitter | |-------|------|----------|----------|---------| | `LOOP_MONITOR_EVENT_SET_COMMITTED` | One atomic Loop Monitor delivery/Judge/latch transition commits | Partition Key, Event Set Id, Event Set | — | `tools/amadeus-loop-monitor-replay.ts` | +| `QUALITY_REPAIR_TRANSACTION_COMMITTED` | One Quality snapshot/progress/replan/stall/resume transaction and its generic Monitor effects commit atomically | Quality Scope Id, Transaction Id, Transaction | — | `tools/amadeus-quality-repair-replay.ts` | ### Swarm (7 events) diff --git a/packages/framework/core/otel/event-registry.ts b/packages/framework/core/otel/event-registry.ts index 8432ceb35..a47cd112f 100644 --- a/packages/framework/core/otel/event-registry.ts +++ b/packages/framework/core/otel/event-registry.ts @@ -1,6 +1,6 @@ // event-registry.ts — the typed Event Registry (FR-EVT-1). // -// The canonical half of the registry covers the full 86-event audit +// The canonical half of the registry covers the full 87-event audit // vocabulary (#1672; VALID_EVENT_TYPES in tools/amadeus-audit.ts) — every // canonical name maps 1:1 onto the EXISTING v1 audit event vocabulary so the // current readers understand the records unchanged. The telemetry half @@ -14,7 +14,7 @@ // sites (BR-2); // 2. unit test — tests/unit/event-registry-drift.test.ts asserts the // four-set equality (state-machine references == canonical registry == -// exporter accept set == journal reader decode set) with the 86 +// exporter accept set == journal reader decode set) with the 87 // cardinality pinned, so vacuous equality fails; // 3. sensor — sensors/amadeus-event-registry-drift.md runs the same // extraction at gate time. @@ -76,7 +76,7 @@ export type EventDef = { // The canonical cardinality (#1672). The drift guard pins this so an emptied // or truncated registry fails instead of passing vacuously. -export const EXPECTED_CANONICAL_COUNT = 86; +export const EXPECTED_CANONICAL_COUNT = 87; // The OTel semantic-convention span event name produced by recordException(). // Registered as telemetry (FR-EVT-7): it rides the span record, never the @@ -206,6 +206,15 @@ export const REGISTERED_EVENTS = [ optionalAttributes: [], schemaVersion: 1, }, + { + name: "amadeus.quality_repair.transaction.committed", + auditEvent: "QUALITY_REPAIR_TRANSACTION_COMMITTED", + durability: "canonical", + category: "loop-monitor", + requiredAttributes: ["Quality Scope Id", "Transaction Id", "Transaction"], + optionalAttributes: [], + schemaVersion: 1, + }, // --- Phase Lifecycle (4) --- { name: "amadeus.phase.started", diff --git a/packages/framework/core/tools/amadeus-audit.ts b/packages/framework/core/tools/amadeus-audit.ts index 6d92aa3c2..fbc08b6ff 100644 --- a/packages/framework/core/tools/amadeus-audit.ts +++ b/packages/framework/core/tools/amadeus-audit.ts @@ -91,6 +91,7 @@ const VALID_EVENT_TYPES = new Set([ // canonical event set; required projections consume its digest after append. "EXECUTION_EVENT_SET_COMMITTED", "LOOP_MONITOR_EVENT_SET_COMMITTED", + "QUALITY_REPAIR_TRANSACTION_COMMITTED", // Session events (hook-owned) "SESSION_STARTED", "SESSION_RESUMED", @@ -242,6 +243,7 @@ export const EVENT_HEADINGS: Record = { LEGACY_GOAL_MIGRATED: "Legacy Goal Migrated", EXECUTION_EVENT_SET_COMMITTED: "Execution Event Set Committed", LOOP_MONITOR_EVENT_SET_COMMITTED: "Loop Monitor Event Set Committed", + QUALITY_REPAIR_TRANSACTION_COMMITTED: "Quality Repair Transaction Committed", UNIT_POOL_EVENT_SET_COMMITTED: "Unit Pool Event Set Committed", SESSION_STARTED: "Session Start", SESSION_RESUMED: "Session Resume", diff --git a/packages/framework/core/tools/amadeus-loop-monitor-replay.ts b/packages/framework/core/tools/amadeus-loop-monitor-replay.ts index 0a1b121c1..4249dcc84 100644 --- a/packages/framework/core/tools/amadeus-loop-monitor-replay.ts +++ b/packages/framework/core/tools/amadeus-loop-monitor-replay.ts @@ -230,7 +230,7 @@ function validEvent(value: unknown): value is LoopMonitorEvent { } } -function validEventSet(value: unknown): value is LoopMonitorEventSet { +export function validEventSet(value: unknown): value is LoopMonitorEventSet { return isRecord(value) && typeof value.eventSetId === "string" && validPartition(value.partition) && typeof value.partitionKey === "string" && value.partitionKey === loopMonitorPartitionKey(value.partition) && diff --git a/packages/framework/core/tools/amadeus-loop-monitor-runtime.ts b/packages/framework/core/tools/amadeus-loop-monitor-runtime.ts index 9ddac5ea8..a67876f47 100644 --- a/packages/framework/core/tools/amadeus-loop-monitor-runtime.ts +++ b/packages/framework/core/tools/amadeus-loop-monitor-runtime.ts @@ -259,6 +259,23 @@ function applyJudgeCompletion( }; } +function applyRoute( + projection: LoopMonitorRuntimeProjection, + monitor: CompiledLoopMonitor, + event: RuntimeEventOf<"LOOP_ROUTE_APPLIED">, +): LoopMonitorRuntimeProjection { + if (event.targetEvent === null) return { ...projection, lastRouteId: event.routeId }; + const cycleIndex = monitor.cycle.indexOf(event.targetEvent); + return { + ...projection, + lastRouteId: event.routeId, + lastSemanticEventId: event.targetEvent, + matchedPrefix: cycleIndex < 0 ? 0 : cycleIndex + 1, + cycleCount: 0, + epoch: projection.epoch + 1, + }; +} + const RUNTIME_EVENT_HANDLERS = { LOOP_DELIVERY_OBSERVED: applyObservedDelivery, LOOP_MONITOR_TRIGGERED: (projection, _monitor, event) => @@ -267,7 +284,7 @@ const RUNTIME_EVENT_HANDLERS = { LOOP_JUDGE_ATTEMPT_STARTED: (projection, _monitor, event) => applyJudgeAttempt(projection, event), LOOP_JUDGE_RESULT_OBSERVED: (projection) => projection, LOOP_JUDGE_COMPLETED: (projection, _monitor, event) => applyJudgeCompletion(projection, event), - LOOP_ROUTE_APPLIED: (projection, _monitor, event) => ({ ...projection, lastRouteId: event.routeId }), + LOOP_ROUTE_APPLIED: (projection, monitor, event) => applyRoute(projection, monitor, event), LOOP_LATCH_SET: (projection, _monitor, event) => ({ ...projection, latch: event.latch, pendingJudge: null }), LOOP_LATCH_CLEARED: (projection) => ({ ...projection, latch: null, awaitingHumanReason: null }), WORKFLOW_UNPARKED: (projection) => projection, @@ -317,7 +334,8 @@ function deliveryReplayDecision( const observed = event.delivery; if (observed.upstreamEventIdentity === delivery.upstreamEventIdentity) { return observed.deliveryId === delivery.deliveryId && - observed.payloadFingerprint === delivery.payloadFingerprint + observed.payloadFingerprint === delivery.payloadFingerprint && + observed.routeConstraint.fingerprint === delivery.routeConstraint.fingerprint ? "duplicate" : "identity-conflict"; } @@ -347,7 +365,7 @@ function judgeRequest( judgeInstructionId: monitor.judgeInstruction.id, evidenceFingerprint: reservation.evidenceFingerprint, routeConstraintFingerprint: reservation.constraintFingerprint, - allowedRouteIds: [...monitor.routeConstraint.routeIds], + allowedRouteIds: [...reservation.routeConstraint.routeIds], trace: { ...reservation.trace }, }; } @@ -365,6 +383,7 @@ function matchingJudgeResult( result.evidenceFingerprint === reservation.evidenceFingerprint && result.constraintFingerprint === reservation.constraintFingerprint && tracesEqual(result.trace, reservation.trace) && + reservation.routeConstraint.routeIds.includes(result.routeId) && routeById(monitor, result.routeId) !== null; } diff --git a/packages/framework/core/tools/amadeus-loop-monitor.ts b/packages/framework/core/tools/amadeus-loop-monitor.ts index a02fceb34..c0d660e85 100644 --- a/packages/framework/core/tools/amadeus-loop-monitor.ts +++ b/packages/framework/core/tools/amadeus-loop-monitor.ts @@ -75,6 +75,38 @@ export interface CompiledLoopMonitorGraph { readonly loopMonitors: readonly CompiledLoopMonitor[]; } +export function createJudgeRouteConstraint( + monitor: Pick, + routeIds: readonly string[], +): JudgeRouteConstraint { + if (routeIds.length === 0) throw new Error("loop-monitor-route-constraint-empty"); + if (new Set(routeIds).size !== routeIds.length) { + throw new Error("loop-monitor-route-constraint-duplicate-route"); + } + const requested = new Set(routeIds); + const routes = monitor.routes.filter((route) => requested.has(route.id)); + if (routes.length !== routeIds.length) { + throw new Error("loop-monitor-route-constraint-unknown-route"); + } + return { + routeIds: routes.map((route) => route.id), + fingerprint: digest(routes), + }; +} + +function isValidJudgeRouteConstraint( + monitor: CompiledLoopMonitor, + constraint: JudgeRouteConstraint, +): boolean { + try { + const expected = createJudgeRouteConstraint(monitor, constraint.routeIds); + return expected.fingerprint === constraint.fingerprint && + expected.routeIds.every((routeId, index) => routeId === constraint.routeIds[index]); + } catch { + return false; + } +} + export type LoopMonitorCompileResult = | { readonly ok: true; readonly graph: CompiledLoopMonitorGraph } | { readonly ok: false; readonly errors: readonly LoopMonitorCompileError[] }; @@ -363,10 +395,7 @@ export function compileLoopMonitorManifest( continue; } const exactRoutes = selectedRoutes as JudgeRouteDescriptor[]; - const routeConstraint: JudgeRouteConstraint = { - routeIds: [...monitor.routes], - fingerprint: digest(exactRoutes), - }; + const routeConstraint = createJudgeRouteConstraint({ routes: exactRoutes }, monitor.routes); compiled.push({ id: monitor.id, cycle: [...monitor.cycle], @@ -503,6 +532,7 @@ export function createLoopDelivery(input: CreateLoopDeliveryInput): LoopDelivery input.partition, input.upstreamEventIdentity, input.payloadFingerprint, + input.routeConstraint.fingerprint, ]).slice("sha256:".length)}`; return immutableDelivery(input, deliveryId); } @@ -515,6 +545,7 @@ export interface LoopJudgeReservation { readonly graphRevision: string; readonly evidenceFingerprint: string; readonly constraintFingerprint: string; + readonly routeConstraint: JudgeRouteConstraint; readonly trace: LoopTraceContext; } @@ -619,6 +650,10 @@ function reserveJudge( graphRevision: projection.partition.graphRevision, evidenceFingerprint: delivery.evidence.fingerprint, constraintFingerprint: delivery.routeConstraint.fingerprint, + routeConstraint: { + routeIds: [...delivery.routeConstraint.routeIds], + fingerprint: delivery.routeConstraint.fingerprint, + }, trace: { ...delivery.trace }, }; } @@ -725,12 +760,16 @@ export function applyLoopDelivery( if (!samePartition(projection.partition, delivery.partition) || delivery.partition.monitorId !== monitor.id) { return { ok: false, status: "CONFLICT", reason: "partition-mismatch" }; } - if (delivery.routeConstraint.fingerprint !== monitor.routeConstraint.fingerprint) { + if (!isValidJudgeRouteConstraint(monitor, delivery.routeConstraint)) { return { ok: false, status: "CONFLICT", reason: "route-constraint-mismatch" }; } const known = knownDelivery(projection, delivery); if (known !== undefined) { - if (known.deliveryId === delivery.deliveryId && known.payloadFingerprint === delivery.payloadFingerprint) { + if ( + known.deliveryId === delivery.deliveryId && + known.payloadFingerprint === delivery.payloadFingerprint && + known.routeConstraint.fingerprint === delivery.routeConstraint.fingerprint + ) { return { ok: true, projection, duplicate: true, pending: false, judgeReservation: null }; } return { ok: false, status: "CONFLICT", reason: "delivery-identity-payload-conflict" }; diff --git a/packages/framework/core/tools/amadeus-quality-repair-replay.ts b/packages/framework/core/tools/amadeus-quality-repair-replay.ts new file mode 100644 index 000000000..d4385d093 --- /dev/null +++ b/packages/framework/core/tools/amadeus-quality-repair-replay.ts @@ -0,0 +1,190 @@ +// Canonical audit adapter and replay entry point for Quality Repair (#2096). + +import { emitAuditEventGuarded } from "../otel/audit-emit.ts"; +import { validEventSet } from "./amadeus-loop-monitor-replay.ts"; +import { qualityDigest } from "./amadeus-quality-repair.ts"; +import { + createMemoryQualityRepairRepository, + projectQualityRepairStatus, + type QualityRepairRepository, + type QualityRepairStatusEnvelope, + type QualityRepairTransaction, + type QualityRuntimeEvent, + type QualityRuntimeProjection, +} from "./amadeus-quality-repair-runtime.ts"; +import { + auditBlockField, + findAllEvents, + readAllAuditShards, + withAuditLock, +} from "./amadeus-lib.ts"; + +const QUALITY_EVENT_TYPES = new Set([ + "QUALITY_SNAPSHOT_OBSERVED", + "QUALITY_REPLAN_RESERVED", + "QUALITY_REPLAN_RECORDED", + "REPAIR_STALLED", + "QUALITY_EPOCH_STARTED", +]); + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function validPartition(value: unknown): boolean { + return isRecord(value) && typeof value.intentUuid === "string" && + typeof value.monitorId === "string" && typeof value.stageInstanceId === "string" && + typeof value.graphRevision === "string"; +} + +function validEpoch(value: unknown, qualityScopeId: string): boolean { + return isRecord(value) && value.qualityScopeId === qualityScopeId && + typeof value.qualityEpochId === "string" && typeof value.epochStartEventIdentity === "string" && + Number.isInteger(value.threshold) && (value.threshold as number) > 0 && Array.isArray(value.window) && + Number.isInteger(value.consecutiveNonProgress) && + typeof value.replanSinceLastProgress === "boolean"; +} + +function validProjectionIdentity(value: Record): boolean { + return typeof value.qualityScopeId === "string" && validPartition(value.partition) && + validEpoch(value.epoch, value.qualityScopeId); +} + +function validProjectionProgress(value: Record): boolean { + return Number.isInteger(value.observationSequence) && (value.observationSequence as number) >= 0 && + (value.workflowExecutionState === "running" || value.workflowExecutionState === "suspended"); +} + +function validProjectionOptionals(value: Record): boolean { + return (value.latestSnapshot === null || + (isRecord(value.latestSnapshot) && Array.isArray(value.latestSnapshot.unresolved) && + Array.isArray(value.latestSnapshot.verifierSuccessReceipts))) && + (value.lastProgress === null || isRecord(value.lastProgress)) && + (value.stalledLatch === null || + (isRecord(value.stalledLatch) && Array.isArray(value.stalledLatch.unresolvedObligationIds))) && + (value.lastReplanReceipt === null || isRecord(value.lastReplanReceipt)) && + (value.pendingReplan === null || + (isRecord(value.pendingReplan) && typeof value.pendingReplan.attemptIdentity === "string")); +} + +function validProjection(value: unknown): value is QualityRuntimeProjection { + return isRecord(value) && validProjectionIdentity(value) && + validProjectionProgress(value) && validProjectionOptionals(value); +} + +function validQualityEventPayload(value: Record): boolean { + switch (value.type as QualityRuntimeEvent["type"]) { + case "QUALITY_SNAPSHOT_OBSERVED": + return typeof value.snapshotFingerprint === "string" && isRecord(value.progress); + case "QUALITY_REPLAN_RESERVED": + return typeof value.replanId === "string" && typeof value.judgeInvocationId === "string" && + (value.attemptNo === 0 || value.attemptNo === 1); + case "QUALITY_REPLAN_RECORDED": + return typeof value.replanId === "string" && isRecord(value.receipt); + case "REPAIR_STALLED": + return isRecord(value.latch); + case "QUALITY_EPOCH_STARTED": + return typeof value.priorQualityEpochId === "string" && + typeof value.satisfiedAlternativeIdentity === "string" && + typeof value.resumeEvidenceReceipt === "string"; + } +} + +function validQualityEvent(value: unknown): value is QualityRuntimeEvent { + return isRecord(value) && typeof value.type === "string" && + QUALITY_EVENT_TYPES.has(value.type as QualityRuntimeEvent["type"]) && + validProjection(value.projection) && validQualityEventPayload(value); +} + +function validTransaction(value: unknown): value is QualityRepairTransaction { + return isRecord(value) && value.schemaVersion === 1 && typeof value.transactionId === "string" && + typeof value.qualityScopeId === "string" && Array.isArray(value.qualityEvents) && + value.qualityEvents.every(validQualityEvent) && Array.isArray(value.loopEventSets) && + value.loopEventSets.every(validEventSet); +} + +export function decodeQualityRepairTransaction(encoded: string): QualityRepairTransaction { + const parsed: unknown = JSON.parse(encoded); + if (!validTransaction(parsed)) throw new Error("invalid-quality-repair-transaction"); + return parsed; +} + +export function readQualityRepairTransactionsFromAudit( + projectDir: string, + intent?: string, + space?: string, +): QualityRepairTransaction[] { + const rows = findAllEvents( + readAllAuditShards(projectDir, intent, space), + "QUALITY_REPAIR_TRANSACTION_COMMITTED", + ); + const transactions = rows.map((row) => { + const encoded = auditBlockField(row.block, "Transaction"); + if (encoded === null) throw new Error("invalid-quality-repair-audit-row:missing-transaction"); + return decodeQualityRepairTransaction(encoded); + }); + const identities = new Map(); + for (const transaction of transactions) { + const encoded = qualityDigest(transaction); + const prior = identities.get(transaction.transactionId); + if (prior !== undefined && prior !== encoded) { + throw new Error("invalid-quality-repair-audit-row:transaction-conflict"); + } + identities.set(transaction.transactionId, encoded); + } + return [...new Map(transactions.map((item) => [item.transactionId, item])).values()]; +} + +export function createAuditQualityRepairRepository(options: { + readonly projectDir: string; + readonly intent?: string; + readonly space?: string; +}): QualityRepairRepository { + const initialTransactions = readQualityRepairTransactionsFromAudit( + options.projectDir, + options.intent, + options.space, + ); + return createMemoryQualityRepairRepository({ + initialTransactions, + onCommit(transaction) { + emitAuditEventGuarded( + "QUALITY_REPAIR_TRANSACTION_COMMITTED", + { + "Quality Scope Id": transaction.qualityScopeId, + "Transaction Id": transaction.transactionId, + Transaction: JSON.stringify(transaction), + }, + options.projectDir, + options.intent, + options.space, + ); + }, + transactionLock: ((body: () => unknown) => + withAuditLock( + options.projectDir, + body as () => never, + options.intent, + options.space, + )) as (body: () => T) => T, + }); +} + +export function replayQualityRepairScope( + repository: QualityRepairRepository, + qualityScopeId: string, +): { + readonly projection: QualityRuntimeProjection; + readonly status: QualityRepairStatusEnvelope; + readonly transactionCount: number; +} { + const projection = repository.readProjection(qualityScopeId); + if (projection === null) throw new Error("quality-repair-replay-scope-not-found"); + return { + projection, + status: projectQualityRepairStatus(projection), + transactionCount: repository.readTransactions().filter( + (transaction) => transaction.qualityScopeId === qualityScopeId, + ).length, + }; +} diff --git a/packages/framework/core/tools/amadeus-quality-repair-runtime.ts b/packages/framework/core/tools/amadeus-quality-repair-runtime.ts new file mode 100644 index 000000000..29ec8f571 --- /dev/null +++ b/packages/framework/core/tools/amadeus-quality-repair-runtime.ts @@ -0,0 +1,951 @@ +// Durable Quality Repair orchestration over the generic Loop Monitor (#2096). + +import { + createJudgeRouteConstraint, + createLoopDelivery, + type CompiledLoopMonitor, + type LoopMonitorPartition, + type LoopTraceContext, +} from "./amadeus-loop-monitor.ts"; +import { + createLoopMonitorCoordinator, + loopMonitorPartitionKey, + loopMonitorReceiptId, + type CommittedJudgeDispatchPermit, + type JudgePort, + type LoopMonitorCommitReceipt, + type LoopMonitorEventSet, + type LoopMonitorRepository, + type VerifiedHumanTurn, +} from "./amadeus-loop-monitor-runtime.ts"; +import { + createQualityEpochProjection, + normalizeQualityEvidence, + planQualityDelivery, + qualityDigest, + qualityStableId, + recordQualityReplan, + type QualityDeliveryPlan, + type QualityEvidenceBatchInput, + type QualityEvidenceSnapshot, + type QualityEpochProjection, + type QualityNonProgressPattern, + type QualityPluginActivation, + type QualityProgress, + type QualityReplanReceipt, +} from "./amadeus-quality-repair.ts"; + +type ActiveQualityActivation = Extract; + +export interface RepairStalledResumeAlternative { + readonly kind: "evidence-change" | "human-retry"; + readonly identity: string; +} + +export interface RepairStalledResumeCondition { + readonly kind: "any-of"; + readonly alternatives: readonly [RepairStalledResumeAlternative, RepairStalledResumeAlternative]; +} + +export interface RepairStalledLatch { + readonly qualityEpochId: string; + readonly evidenceFingerprint: string; + readonly unresolvedObligationIds: readonly string[]; + readonly verifierSuccessReceipts: QualityEvidenceSnapshot["verifierSuccessReceipts"]; + readonly pattern: QualityNonProgressPattern; + readonly judgeInvocationId: string; + readonly replanBasis: string | null; + readonly resumeCondition: RepairStalledResumeCondition; +} + +export interface QualityRuntimeProjection { + readonly qualityScopeId: string; + readonly partition: LoopMonitorPartition; + readonly epoch: QualityEpochProjection; + readonly latestSnapshot: QualityEvidenceSnapshot | null; + readonly lastProgress: QualityProgress | null; + readonly observationSequence: number; + readonly stalledLatch: RepairStalledLatch | null; + readonly workflowExecutionState: "running" | "suspended"; + readonly lastReplanReceipt: QualityReplanReceipt | null; + readonly pendingReplan: QualityReplanRequest | null; +} + +export type QualityRuntimeEvent = + | { + readonly type: "QUALITY_SNAPSHOT_OBSERVED"; + readonly snapshotFingerprint: string; + readonly progress: QualityProgress; + readonly projection: QualityRuntimeProjection; + } + | { + readonly type: "QUALITY_REPLAN_RESERVED"; + readonly replanId: string; + readonly judgeInvocationId: string; + readonly attemptNo: 0 | 1; + readonly projection: QualityRuntimeProjection; + } + | { + readonly type: "QUALITY_REPLAN_RECORDED"; + readonly replanId: string; + readonly receipt: QualityReplanReceipt; + readonly projection: QualityRuntimeProjection; + } + | { + readonly type: "REPAIR_STALLED"; + readonly latch: RepairStalledLatch; + readonly projection: QualityRuntimeProjection; + } + | { + readonly type: "QUALITY_EPOCH_STARTED"; + readonly priorQualityEpochId: string; + readonly satisfiedAlternativeIdentity: string; + readonly resumeEvidenceReceipt: string; + readonly projection: QualityRuntimeProjection; + }; + +export interface QualityRepairTransaction { + readonly schemaVersion: 1; + readonly transactionId: string; + readonly qualityScopeId: string; + readonly qualityEvents: readonly QualityRuntimeEvent[]; + readonly loopEventSets: readonly LoopMonitorEventSet[]; +} + +export interface QualityRepairRepository { + readonly loopRepository: LoopMonitorRepository; + transaction( + qualityScopeId: string, + body: (appendQuality: (event: QualityRuntimeEvent) => void) => T, + ): T; + readProjection(qualityScopeId: string): QualityRuntimeProjection | null; + readProjectionByPartition(partition: LoopMonitorPartition): QualityRuntimeProjection | null; + readTransactions(): readonly QualityRepairTransaction[]; +} + +function loopReceipt(set: LoopMonitorEventSet): LoopMonitorCommitReceipt { + const eventSetDigest = qualityDigest(set); + return { + receiptId: loopMonitorReceiptId(set.eventSetId, eventSetDigest), + eventSetId: set.eventSetId, + eventSetDigest, + partitionKey: set.partitionKey, + }; +} + +function foldQualityProjections( + transactions: readonly QualityRepairTransaction[], +): ReadonlyMap { + const projections = new Map(); + const eventDigests = new Map>(); + for (const transaction of transactions) { + const transactionDigest = qualityDigest(transaction); + const scopeDigests = eventDigests.get(transaction.qualityScopeId) ?? new Map(); + const prior = scopeDigests.get(transaction.transactionId); + if (prior !== undefined) { + if (prior !== transactionDigest) throw new Error("quality-repair-transaction-conflict"); + continue; + } + scopeDigests.set(transaction.transactionId, transactionDigest); + eventDigests.set(transaction.qualityScopeId, scopeDigests); + for (const event of transaction.qualityEvents) { + if (event.projection.qualityScopeId !== transaction.qualityScopeId) { + throw new Error("quality-repair-projection-scope-mismatch"); + } + projections.set(transaction.qualityScopeId, event.projection); + } + } + return projections; +} + +export function createMemoryQualityRepairRepository(options: { + readonly initialTransactions?: readonly QualityRepairTransaction[]; + readonly onCommit?: (transaction: QualityRepairTransaction) => void; + readonly transactionLock?: (body: () => T) => T; +} = {}): QualityRepairRepository { + const transactions: QualityRepairTransaction[] = [...(options.initialTransactions ?? [])]; + const lock = options.transactionLock ?? ((body: () => T): T => body()); + let projectionCache: ReadonlyMap | null = null; + let partitionIndex: ReadonlyMap | null = null; + let active: { + readonly qualityScopeId: string; + readonly qualityEvents: QualityRuntimeEvent[]; + readonly loopEventSets: LoopMonitorEventSet[]; + } | null = null; + + function projections(): ReadonlyMap { + if (projectionCache !== null) return projectionCache; + const nextProjections = foldQualityProjections(transactions); + const nextIndex = new Map(); + for (const [qualityScopeId, projection] of nextProjections) { + const key = loopMonitorPartitionKey(projection.partition); + if (!nextIndex.has(key)) nextIndex.set(key, qualityScopeId); + } + projectionCache = nextProjections; + partitionIndex = nextIndex; + return nextProjections; + } + + function invalidateProjections(): void { + projectionCache = null; + partitionIndex = null; + } + + function allLoopSets(partition: LoopMonitorPartition): LoopMonitorEventSet[] { + const key = loopMonitorPartitionKey(partition); + const committed = transactions.flatMap((transaction) => transaction.loopEventSets) + .filter((set) => set.partitionKey === key); + const staged = active?.loopEventSets.filter((set) => set.partitionKey === key) ?? []; + return [...committed, ...staged]; + } + + const loopRepository: LoopMonitorRepository = { + transaction(partition, body) { + if (active !== null) { + return body(allLoopSets(partition), (set) => { + if (set.partitionKey !== loopMonitorPartitionKey(partition)) { + throw new Error("quality-repair-loop-partition-mismatch"); + } + const existing = allLoopSets(partition).find((candidate) => candidate.eventSetId === set.eventSetId); + if (existing !== undefined) { + if (qualityDigest(existing) !== qualityDigest(set)) { + throw new Error("quality-repair-loop-event-set-conflict"); + } + return loopReceipt(existing); + } + active!.loopEventSets.push(set); + return loopReceipt(set); + }); + } + const qualityScopeId = qualityStableId("quality-loop-only", [loopMonitorPartitionKey(partition)]); + const notExecuted = Symbol("quality-repair-loop-only-transaction-not-executed"); + let result: ReturnType | typeof notExecuted = notExecuted; + repository.transaction(qualityScopeId, () => { + result = loopRepository.transaction(partition, body); + }); + if (result === notExecuted) throw new Error("quality-repair-loop-only-transaction-not-executed"); + return result; + }, + readEventSets(partition) { + return allLoopSets(partition); + }, + isCommitted(receipt) { + const sets = transactions.flatMap((transaction) => transaction.loopEventSets); + const staged = active?.loopEventSets ?? []; + return [...sets, ...staged].some((set) => { + const expected = loopReceipt(set); + return expected.receiptId === receipt.receiptId && expected.eventSetId === receipt.eventSetId && + expected.eventSetDigest === receipt.eventSetDigest && expected.partitionKey === receipt.partitionKey; + }); + }, + }; + + const repository: QualityRepairRepository = { + loopRepository, + transaction(qualityScopeId, body) { + if (active !== null) { + if (active.qualityScopeId !== qualityScopeId) throw new Error("quality-repair-nested-scope-mismatch"); + return body((event) => active!.qualityEvents.push(event)); + } + return lock(() => { + const staged = { qualityScopeId, qualityEvents: [] as QualityRuntimeEvent[], loopEventSets: [] as LoopMonitorEventSet[] }; + active = staged; + try { + const result = body((event) => staged.qualityEvents.push(event)); + if (staged.qualityEvents.length > 0 || staged.loopEventSets.length > 0) { + const transactionId = qualityStableId("quality-transaction", [ + qualityScopeId, + transactions.length, + staged.qualityEvents, + staged.loopEventSets, + ]); + const transaction = { schemaVersion: 1 as const, transactionId, ...staged }; + options.onCommit?.(transaction); + transactions.push(transaction); + invalidateProjections(); + } + return result; + } finally { + active = null; + } + }); + }, + readProjection(qualityScopeId) { + return projections().get(qualityScopeId) ?? null; + }, + readProjectionByPartition(partition) { + const cached = projections(); + const qualityScopeId = partitionIndex?.get(loopMonitorPartitionKey(partition)); + return qualityScopeId === undefined ? null : cached.get(qualityScopeId) ?? null; + }, + readTransactions() { + return transactions.map((transaction) => ({ + ...transaction, + qualityEvents: [...transaction.qualityEvents], + loopEventSets: [...transaction.loopEventSets], + })); + }, + }; + return repository; +} + +export interface QualityReplanRequest { + readonly replanId: string; + readonly qualityScopeId: string; + readonly qualityEpochId: string; + readonly triggerSnapshotFingerprint: string; + readonly judgeInvocationId: string; + readonly attemptNo: 0 | 1; + readonly attemptIdentity: string; + readonly priorNoEffectReceiptDigest: string | null; +} + +export type QualityReplanDispatchResult = + | { readonly kind: "completed"; readonly receipt: QualityReplanReceipt } + | { readonly kind: "accepted"; readonly operationRef: string } + | { readonly kind: "no-effect-confirmed"; readonly attestationId: string } + | { readonly kind: "effect-possible" | "unknown"; readonly reason: string }; + +export interface QualityReplanPort { + dispatch(request: QualityReplanRequest): QualityReplanDispatchResult; + reconcile(request: QualityReplanRequest): QualityReplanDispatchResult; +} + +export type QualityObserveResult = + | { + readonly kind: "repair"; + readonly snapshot: QualityEvidenceSnapshot; + readonly progress: QualityProgress; + readonly projection: QualityEpochProjection; + } + | { + readonly kind: "judge-reserved"; + readonly snapshot: QualityEvidenceSnapshot; + readonly progress: QualityProgress; + readonly permit: CommittedJudgeDispatchPermit; + readonly projection: QualityEpochProjection; + } + | { + readonly kind: "REPAIR_STALLED"; + readonly snapshot: QualityEvidenceSnapshot; + readonly progress: QualityProgress; + readonly projection: QualityEpochProjection; + } + | { + readonly kind: "CONFLICT" | "INCOMPLETE"; + readonly reason: string; + readonly snapshot: QualityEvidenceSnapshot; + readonly progress: QualityProgress; + readonly projection: QualityEpochProjection; + }; + +export type QualityJudgeResult = + | { readonly kind: "replanned"; readonly projection: QualityEpochProjection } + | { readonly kind: "replan-pending"; readonly request: QualityReplanRequest } + | { readonly kind: "REPAIR_STALLED"; readonly projection: QualityEpochProjection } + | { readonly kind: "AWAITING_HUMAN" | "CONFLICT" | "INCOMPLETE"; readonly reason: string }; + +export interface QualityRepairStatusEnvelope { + readonly outcome: "active" | "parked"; + readonly qualityScopeId: string; + readonly qualityEpochId: string; + readonly pattern: QualityNonProgressPattern | null; + readonly threshold: number; + readonly consecutiveNonProgress: number; + readonly replanSinceLastProgress: boolean; + readonly unresolvedObligationIds: readonly string[]; + readonly evidenceFingerprint: string | null; + readonly replanBasis: string | null; + readonly stopReason: "REPAIR_STALLED" | null; + readonly workflowExecutionState: "running" | "suspended"; + readonly grantExplanation: "quality-repair-does-not-modify-grants"; + readonly resumeCondition: RepairStalledResumeCondition; +} + +function emptyResumeCondition(qualityScopeId: string): RepairStalledResumeCondition { + return { + kind: "any-of", + alternatives: [ + { kind: "evidence-change", identity: qualityStableId("quality-resume-evidence", qualityScopeId) }, + { kind: "human-retry", identity: qualityStableId("quality-resume-human", qualityScopeId) }, + ], + }; +} + +function runtimeProjection( + partition: LoopMonitorPartition, + epoch: QualityEpochProjection, + snapshot: QualityEvidenceSnapshot, + progress: QualityProgress, + previous: QualityRuntimeProjection | null, +): QualityRuntimeProjection { + return { + qualityScopeId: snapshot.qualityScopeId, + partition, + epoch, + latestSnapshot: snapshot, + lastProgress: progress, + observationSequence: (previous?.observationSequence ?? 0) + 1, + stalledLatch: null, + workflowExecutionState: "running", + lastReplanReceipt: previous?.lastReplanReceipt ?? null, + pendingReplan: previous?.pendingReplan ?? null, + }; +} + +function progressPattern(progress: QualityProgress | null): QualityNonProgressPattern | null { + return progress?.kind === "threshold" ? progress.pattern : null; +} + +export function renderQualityRepairStatus(status: QualityRepairStatusEnvelope): string { + return [ + `Quality Repair: ${status.qualityScopeId}`, + `Outcome: ${status.outcome}`, + `Epoch: ${status.qualityEpochId}`, + `Progress: ${status.consecutiveNonProgress}/${status.threshold}`, + `Pattern: ${status.pattern ?? "none"}`, + `Workflow: ${status.workflowExecutionState}`, + `Stop reason: ${status.stopReason ?? "none"}`, + `Resume: any-of[${status.resumeCondition.alternatives.map((item) => item.kind).join(",")}]`, + ].join("\n"); +} + +export function projectQualityRepairStatus( + projection: QualityRuntimeProjection, +): QualityRepairStatusEnvelope { + const latch = projection.stalledLatch; + return { + outcome: latch === null ? "active" : "parked", + qualityScopeId: projection.qualityScopeId, + qualityEpochId: projection.epoch.qualityEpochId, + pattern: latch?.pattern ?? progressPattern(projection.lastProgress), + threshold: projection.epoch.threshold, + consecutiveNonProgress: projection.epoch.consecutiveNonProgress, + replanSinceLastProgress: projection.epoch.replanSinceLastProgress, + unresolvedObligationIds: projection.latestSnapshot?.unresolved.map((item) => item.obligationId) ?? [], + evidenceFingerprint: projection.latestSnapshot?.snapshotFingerprint ?? null, + replanBasis: latch?.replanBasis ?? projection.lastReplanReceipt?.planDigest ?? null, + stopReason: latch === null ? null : "REPAIR_STALLED", + workflowExecutionState: projection.workflowExecutionState, + grantExplanation: "quality-repair-does-not-modify-grants", + resumeCondition: latch?.resumeCondition ?? emptyResumeCondition(projection.qualityScopeId), + }; +} + +function createPartition(snapshot: QualityEvidenceSnapshot): LoopMonitorPartition { + return { + intentUuid: snapshot.intentUuid, + monitorId: snapshot.monitorId, + stageInstanceId: snapshot.stageInstanceId, + graphRevision: snapshot.graphRevision, + }; +} + +function qualityDelivery( + monitor: CompiledLoopMonitor, + runtime: QualityRuntimeProjection, + plan: QualityDeliveryPlan, + eventId: "QUALITY_CHECK" | "QUALITY_NON_PROGRESS" | "QUALITY_STRICT_PROGRESS", + predecessorDeliveryId: string | null, + trace: LoopTraceContext, +) { + const routeConstraint = plan.routeIds.length === 0 + ? monitor.routeConstraint + : createJudgeRouteConstraint(monitor, plan.routeIds); + const snapshot = runtime.latestSnapshot!; + return createLoopDelivery({ + partition: runtime.partition, + eventId, + predecessorDeliveryId, + upstreamEventIdentity: qualityStableId("quality-observation", [ + runtime.epoch.qualityEpochId, + runtime.observationSequence, + eventId, + ]), + payloadFingerprint: qualityDigest([ + snapshot.snapshotFingerprint, + plan.progress, + eventId, + ]), + payload: { + stageId: snapshot.stageInstanceId, + references: [{ + kind: "quality-snapshot", + id: qualityStableId("snapshot", snapshot.snapshotFingerprint), + digest: snapshot.snapshotFingerprint, + }], + }, + evidence: { + fingerprint: snapshot.snapshotFingerprint, + obligationIds: snapshot.unresolved.map((item) => item.obligationId), + }, + routeConstraint, + trace, + }); +} + +function resumeEvidenceImproved( + snapshot: QualityEvidenceSnapshot, + latch: RepairStalledLatch, +): boolean { + const candidateIds = new Set(snapshot.unresolved.map((item) => item.obligationId)); + const latchedIds = new Set(latch.unresolvedObligationIds); + const strictSubset = candidateIds.size < latchedIds.size && [...candidateIds].every((id) => latchedIds.has(id)); + const newSuccess = snapshot.verifierSuccessReceipts.some((receipt) => + latchedIds.has(receipt.obligationId) && !latch.verifierSuccessReceipts.some( + (priorReceipt) => priorReceipt.obligationId === receipt.obligationId && + priorReceipt.receiptDigest === receipt.receiptDigest, + ) + ); + return strictSubset || newSuccess; +} + +interface QualityResumeRequest { + readonly qualityScopeId: string; + readonly alternativeIdentity: string; + readonly humanRetry?: VerifiedHumanTurn; + readonly evidence?: QualityEvidenceBatchInput; + readonly trace?: LoopTraceContext; +} + +type QualityResumeBasis = + | { + readonly ok: true; + readonly kind: "human-retry"; + readonly receipt: string; + readonly humanRetry: VerifiedHumanTurn; + } + | { + readonly ok: true; + readonly kind: "evidence-change"; + readonly receipt: string; + readonly evidence: QualityEvidenceBatchInput; + readonly trace: LoopTraceContext; + } + | { + readonly ok: false; + readonly result: { readonly kind: "CONFLICT" | "INCOMPLETE"; readonly reason: string }; + }; + +function resolveResumeBasis( + alternative: RepairStalledResumeAlternative, + request: QualityResumeRequest, + latch: RepairStalledLatch, +): QualityResumeBasis { + if (alternative.kind === "human-retry") { + if (request.humanRetry?.verified !== true || request.humanRetry.eventType !== "HUMAN_TURN") { + return { ok: false, result: { kind: "CONFLICT", reason: "quality-human-retry-not-verified" } }; + } + return { + ok: true, + kind: "human-retry", + receipt: qualityDigest([request.humanRetry.turnId, alternative.identity]), + humanRetry: request.humanRetry, + }; + } + if (request.evidence === undefined) { + return { ok: false, result: { kind: "CONFLICT", reason: "quality-resume-evidence-missing" } }; + } + const normalized = normalizeQualityEvidence(request.evidence); + if (!normalized.ok) { + return { + ok: false, + result: { + kind: normalized.error.code === "INCOMPLETE" ? "INCOMPLETE" : "CONFLICT", + reason: normalized.error.message, + }, + }; + } + if (!resumeEvidenceImproved(normalized.snapshot, latch)) { + return { ok: false, result: { kind: "CONFLICT", reason: "quality-evidence-did-not-improve" } }; + } + if (request.trace === undefined) { + return { ok: false, result: { kind: "CONFLICT", reason: "quality-resume-trace-missing" } }; + } + return { + ok: true, + kind: "evidence-change", + receipt: qualityDigest(normalized.snapshot.verifierSuccessReceipts), + evidence: request.evidence, + trace: request.trace, + }; +} + +function rebaseResumeEvidence( + basis: Extract, + epochStartEventIdentity: string, +): + | { readonly ok: true; readonly snapshot: QualityEvidenceSnapshot | null } + | { readonly ok: false; readonly result: { readonly kind: "CONFLICT" | "INCOMPLETE"; readonly reason: string } } { + if (basis.kind === "human-retry") return { ok: true, snapshot: null }; + const normalized = normalizeQualityEvidence({ + ...basis.evidence, + previousSnapshot: null, + epochStartEventIdentity, + }); + if (normalized.ok) return { ok: true, snapshot: normalized.snapshot }; + return { + ok: false, + result: { + kind: normalized.error.code === "INCOMPLETE" ? "INCOMPLETE" : "CONFLICT", + reason: normalized.error.message, + }, + }; +} + +export interface QualityRepairCoordinator { + recordEvidence(input: QualityEvidenceBatchInput, trace: LoopTraceContext): QualityObserveResult; + dispatchJudge( + permit: CommittedJudgeDispatchPermit, + judgePort: JudgePort, + replanPort: QualityReplanPort, + ): QualityJudgeResult; + resumeReplan(qualityScopeId: string, replanPort: QualityReplanPort): QualityJudgeResult; + resume(request: QualityResumeRequest): + | { readonly kind: "resumed"; readonly projection: QualityEpochProjection } + | { readonly kind: "CONFLICT" | "INCOMPLETE"; readonly reason: string }; + status(qualityScopeId: string): QualityRepairStatusEnvelope | null; +} + +export function createQualityRepairCoordinator(options: { + readonly activation: ActiveQualityActivation; + readonly repository: QualityRepairRepository; +}): QualityRepairCoordinator { + const { activation, repository } = options; + const monitor = activation.graph.loopMonitors[0]; + if (monitor === undefined) throw new Error("quality-repair-monitor-missing"); + const loop = createLoopMonitorCoordinator({ graph: activation.graph, repository: repository.loopRepository }); + const threshold = monitor.threshold; + + function readByPartition(partition: LoopMonitorPartition): QualityRuntimeProjection | null { + return repository.readProjectionByPartition(partition); + } + + function status(qualityScopeId: string): QualityRepairStatusEnvelope | null { + const projection = repository.readProjection(qualityScopeId); + return projection === null ? null : projectQualityRepairStatus(projection); + } + + function completeReplan( + qualityScopeId: string, + request: QualityReplanRequest, + receipt: QualityReplanReceipt, + ): QualityJudgeResult { + const current = repository.readProjection(qualityScopeId); + if (current === null || current.pendingReplan?.attemptIdentity !== request.attemptIdentity) { + return { kind: "CONFLICT", reason: "quality-replan-attempt-not-pending" }; + } + const recorded = recordQualityReplan(current.epoch, receipt); + const projection: QualityRuntimeProjection = { + ...current, + epoch: recorded.nextProjection, + lastReplanReceipt: receipt, + pendingReplan: null, + }; + repository.transaction(qualityScopeId, (appendQuality) => { + appendQuality({ + type: "QUALITY_REPLAN_RECORDED", + replanId: recorded.replanId, + receipt, + projection, + }); + }); + return { kind: "replanned", projection: recorded.nextProjection }; + } + + function processReplanOutcome( + request: QualityReplanRequest, + outcome: QualityReplanDispatchResult, + replanPort: QualityReplanPort, + ): QualityJudgeResult { + if (outcome.kind === "completed") { + return completeReplan(request.qualityScopeId, request, outcome.receipt); + } + if (outcome.kind === "accepted") return { kind: "replan-pending", request }; + if (outcome.kind !== "no-effect-confirmed") { + return { kind: "AWAITING_HUMAN", reason: `quality-replan-${outcome.kind}` }; + } + if (request.attemptNo === 1) { + return { kind: "AWAITING_HUMAN", reason: "quality-replan-attempts-exhausted" }; + } + const current = repository.readProjection(request.qualityScopeId); + if (current === null || current.pendingReplan?.attemptIdentity !== request.attemptIdentity) { + return { kind: "CONFLICT", reason: "quality-replan-attempt-not-pending" }; + } + const priorNoEffectReceiptDigest = qualityDigest([ + request.attemptIdentity, + outcome.attestationId, + request.qualityScopeId, + ]); + const successor: QualityReplanRequest = { + ...request, + attemptNo: 1, + priorNoEffectReceiptDigest, + attemptIdentity: qualityStableId("quality-replan-attempt", [ + request.replanId, + 1, + priorNoEffectReceiptDigest, + ]), + }; + const projection: QualityRuntimeProjection = { ...current, pendingReplan: successor }; + repository.transaction(request.qualityScopeId, (appendQuality) => { + appendQuality({ + type: "QUALITY_REPLAN_RESERVED", + replanId: successor.replanId, + judgeInvocationId: successor.judgeInvocationId, + attemptNo: 1, + projection, + }); + }); + return processReplanOutcome(successor, replanPort.dispatch(successor), replanPort); + } + + return { + recordEvidence(input, trace) { + const priorByInput = input.previousSnapshot === null + ? null + : repository.readProjection(input.previousSnapshot.qualityScopeId); + if (priorByInput !== null && priorByInput.latestSnapshot !== null && + priorByInput.latestSnapshot.snapshotFingerprint !== input.previousSnapshot?.snapshotFingerprint) { + const snapshot = priorByInput.latestSnapshot; + return { + kind: "CONFLICT", + reason: "quality-previous-snapshot-not-current", + snapshot, + progress: priorByInput.lastProgress!, + projection: priorByInput.epoch, + }; + } + const normalized = normalizeQualityEvidence(input); + if (!normalized.ok) { + throw new Error(`quality-evidence-${normalized.error.code}:${normalized.error.message}`); + } + const snapshot = normalized.snapshot; + const prior = repository.readProjection(snapshot.qualityScopeId); + if (prior !== null && prior.stalledLatch !== null && + prior.stalledLatch.evidenceFingerprint === snapshot.snapshotFingerprint) { + return { + kind: "REPAIR_STALLED", + snapshot: prior.latestSnapshot!, + progress: prior.lastProgress!, + projection: prior.epoch, + }; + } + const epoch = prior?.epoch ?? createQualityEpochProjection(snapshot, threshold); + const plan = planQualityDelivery(epoch, snapshot); + const partition = prior?.partition ?? createPartition(snapshot); + const nextRuntime = runtimeProjection(partition, plan.nextProjection, snapshot, plan.progress, prior); + return repository.transaction(snapshot.qualityScopeId, (appendQuality) => { + const observation: QualityRuntimeEvent = { + type: "QUALITY_SNAPSHOT_OBSERVED", + snapshotFingerprint: snapshot.snapshotFingerprint, + progress: plan.progress, + projection: nextRuntime, + }; + const currentLoop = loop.readProjection(partition); + const delivery = qualityDelivery( + monitor, + nextRuntime, + plan, + plan.monitorEvent, + currentLoop.chainHead, + trace, + ); + const observed = loop.observeDelivery(delivery); + if (plan.progress.kind === "threshold") { + if (observed.kind !== "judge-reserved") { + return { + kind: observed.kind === "INCOMPLETE" ? "INCOMPLETE" : "CONFLICT", + reason: "quality-threshold-did-not-reserve-judge", + snapshot, + progress: plan.progress, + projection: plan.nextProjection, + }; + } + appendQuality(observation); + return { + kind: "judge-reserved", + snapshot, + progress: plan.progress, + permit: observed.permit, + projection: plan.nextProjection, + }; + } + if (observed.kind === "CONFLICT" || observed.kind === "INCOMPLETE") { + return { kind: observed.kind, reason: observed.reason, snapshot, progress: plan.progress, projection: plan.nextProjection }; + } + if (plan.progress.kind !== "strict-progress") { + const action = qualityDelivery( + monitor, + nextRuntime, + plan, + "QUALITY_NON_PROGRESS", + delivery.deliveryId, + trace, + ); + const actionResult = loop.observeDelivery(action); + if (actionResult.kind === "CONFLICT" || actionResult.kind === "INCOMPLETE") { + return { + kind: actionResult.kind, + reason: actionResult.reason, + snapshot, + progress: plan.progress, + projection: plan.nextProjection, + }; + } + } + appendQuality(observation); + return { kind: "repair", snapshot, progress: plan.progress, projection: plan.nextProjection }; + }); + }, + + dispatchJudge(permit, judgePort, replanPort) { + const prior = readByPartition(permit.partition); + if (prior === null || prior.latestSnapshot === null) return { kind: "CONFLICT", reason: "quality-projection-not-found" }; + const judgeResult = repository.transaction(prior.qualityScopeId, (appendQuality) => { + const result = loop.dispatchJudge(permit, judgePort); + if (result.kind === "latched") { + const progress = prior.lastProgress; + if (progress?.kind !== "threshold" || progress.requiredRoute !== "repair-stalled") { + return { result: { kind: "CONFLICT", reason: "quality-stall-route-out-of-order" } as QualityJudgeResult, request: null }; + } + const latch: RepairStalledLatch = { + qualityEpochId: prior.epoch.qualityEpochId, + evidenceFingerprint: prior.latestSnapshot!.snapshotFingerprint, + unresolvedObligationIds: prior.latestSnapshot!.unresolved.map((item) => item.obligationId), + verifierSuccessReceipts: prior.latestSnapshot!.verifierSuccessReceipts, + pattern: progress.pattern, + judgeInvocationId: permit.invocationId, + replanBasis: prior.lastReplanReceipt?.planDigest ?? null, + resumeCondition: emptyResumeCondition(prior.qualityScopeId), + }; + const projection: QualityRuntimeProjection = { + ...prior, + stalledLatch: latch, + workflowExecutionState: "suspended", + }; + appendQuality({ type: "REPAIR_STALLED", latch, projection }); + return { result: { kind: "REPAIR_STALLED", projection: projection.epoch } as QualityJudgeResult, request: null }; + } + if (result.kind !== "route-applied" || result.routeId !== "replan") { + const reason = result.kind === "CONFLICT" || result.kind === "INCOMPLETE" || result.kind === "AWAITING_HUMAN" + ? result.reason + : "quality-judge-route-not-replan"; + const kind = result.kind === "AWAITING_HUMAN" ? "AWAITING_HUMAN" : "CONFLICT"; + return { result: { kind, reason } as QualityJudgeResult, request: null }; + } + const replanId = qualityStableId("quality-replan", [ + prior.epoch.qualityEpochId, + prior.latestSnapshot!.snapshotFingerprint, + permit.invocationId, + ]); + const request: QualityReplanRequest = { + replanId, + qualityScopeId: prior.qualityScopeId, + qualityEpochId: prior.epoch.qualityEpochId, + triggerSnapshotFingerprint: prior.latestSnapshot!.snapshotFingerprint, + judgeInvocationId: permit.invocationId, + attemptNo: 0, + attemptIdentity: qualityStableId("quality-replan-attempt", [replanId, 0, null]), + priorNoEffectReceiptDigest: null, + }; + const projection: QualityRuntimeProjection = { ...prior, pendingReplan: request }; + appendQuality({ + type: "QUALITY_REPLAN_RESERVED", + replanId, + judgeInvocationId: permit.invocationId, + attemptNo: 0, + projection, + }); + return { result: null, request }; + }); + if (judgeResult.result !== null) return judgeResult.result; + const request = judgeResult.request!; + return processReplanOutcome(request, replanPort.dispatch(request), replanPort); + }, + + resumeReplan(qualityScopeId, replanPort) { + const projection = repository.readProjection(qualityScopeId); + const request = projection?.pendingReplan; + if (request === null || request === undefined) { + return { kind: "CONFLICT", reason: "quality-replan-attempt-not-pending" }; + } + return processReplanOutcome(request, replanPort.reconcile(request), replanPort); + }, + + resume(request) { + const prior = repository.readProjection(request.qualityScopeId); + if (prior === null || prior.stalledLatch === null || prior.latestSnapshot === null) { + return { kind: "CONFLICT", reason: "quality-stalled-latch-not-found" }; + } + const latch = prior.stalledLatch; + const alternative = latch.resumeCondition.alternatives.find((item) => item.identity === request.alternativeIdentity); + if (alternative === undefined) return { kind: "CONFLICT", reason: "quality-resume-alternative-mismatch" }; + const basis = resolveResumeBasis(alternative, request, latch); + if (!basis.ok) return basis.result; + const epochStartEventIdentity = qualityStableId("quality-epoch-start", [ + prior.epoch.qualityEpochId, + alternative.identity, + basis.receipt, + ]); + const resumedEvidence = rebaseResumeEvidence(basis, epochStartEventIdentity); + if (!resumedEvidence.ok) return resumedEvidence.result; + const resumedSnapshot = resumedEvidence.snapshot; + const template: QualityEvidenceSnapshot = { + ...(resumedSnapshot ?? prior.latestSnapshot), + epochStartEventIdentity, + qualityEpochId: resumedSnapshot?.qualityEpochId ?? + qualityStableId("quality-epoch", [prior.qualityScopeId, epochStartEventIdentity]), + }; + const initialEpoch = createQualityEpochProjection(template, prior.epoch.threshold); + const resumePlan = resumedSnapshot === null ? null : planQualityDelivery(initialEpoch, resumedSnapshot); + const nextEpoch = resumePlan?.nextProjection ?? initialEpoch; + const projection: QualityRuntimeProjection = { + ...prior, + epoch: nextEpoch, + latestSnapshot: resumedSnapshot, + lastProgress: null, + observationSequence: 0, + stalledLatch: null, + workflowExecutionState: "running", + lastReplanReceipt: null, + pendingReplan: null, + }; + return repository.transaction(prior.qualityScopeId, (appendQuality) => { + if (basis.kind === "human-retry") { + const cleared = loop.clearLatch({ + partition: prior.partition, + humanRetry: basis.humanRetry, + }); + if (cleared.kind !== "cleared") { + return { kind: "CONFLICT", reason: "quality-loop-latch-clear-failed" } as const; + } + } else { + const currentLoop = loop.readProjection(prior.partition); + const delivery = qualityDelivery( + monitor, + projection, + resumePlan!, + "QUALITY_STRICT_PROGRESS", + currentLoop.chainHead, + basis.trace, + ); + const observed = loop.observeDelivery(delivery); + if (observed.kind !== "observed") { + return { kind: "CONFLICT", reason: "quality-loop-evidence-resume-failed" } as const; + } + } + appendQuality({ + type: "QUALITY_EPOCH_STARTED", + priorQualityEpochId: prior.epoch.qualityEpochId, + satisfiedAlternativeIdentity: alternative.identity, + resumeEvidenceReceipt: basis.receipt, + projection, + }); + return { kind: "resumed", projection: nextEpoch } as const; + }); + }, + + status, + }; +} diff --git a/packages/framework/core/tools/amadeus-quality-repair.ts b/packages/framework/core/tools/amadeus-quality-repair.ts new file mode 100644 index 000000000..16e442c10 --- /dev/null +++ b/packages/framework/core/tools/amadeus-quality-repair.ts @@ -0,0 +1,838 @@ +// Harness-neutral first-party Quality Repair contribution and convergence model (#2096). + +import { createHash } from "node:crypto"; + +import { + compileLoopMonitorManifest, + type CompiledLoopMonitorGraph, + type LoopMonitorContributions, +} from "./amadeus-loop-monitor.ts"; +import type { VerifiedHumanTurn } from "./amadeus-loop-monitor-runtime.ts"; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function bytewise(a: string, b: string): number { + return Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")); +} + +function canonicalJson(value: unknown): string { + if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`; + if (isRecord(value)) { + return `{${Object.keys(value).sort(bytewise).map((key) => + `${JSON.stringify(key)}:${canonicalJson(value[key])}` + ).join(",")}}`; + } + return JSON.stringify(value) ?? "null"; +} + +function digest(value: unknown): string { + return `sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`; +} + +function stableId(namespace: string, value: unknown): string { + return `${namespace}-${digest(value).slice("sha256:".length, "sha256:".length + 32)}`; +} + +const SHA256 = /^sha256:[0-9a-f]{64}$/; +const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/; + +export type QualityAutonomyMode = "none" | "semi" | "full"; + +export interface QualityPluginProjection { + readonly intentUuid: string; + readonly noneModeOptedIn: boolean; + readonly provenanceTurnId: string | null; + readonly projectionRevision: number; +} + +export function emptyQualityPluginProjection(intentUuid: string): QualityPluginProjection { + return { + intentUuid, + noneModeOptedIn: false, + provenanceTurnId: null, + projectionRevision: 0, + }; +} + +export type QualitySettingPlan = + | { + readonly ok: true; + readonly projection: QualityPluginProjection; + readonly event: { + readonly type: "QUALITY_REPAIR_OPTED_IN" | "QUALITY_REPAIR_OPTED_OUT"; + readonly intentUuid: string; + readonly humanTurnId: string; + }; + } + | { + readonly ok: false; + readonly error: { readonly code: "PROVENANCE_REQUIRED" | "INTENT_MISMATCH"; readonly message: string }; + }; + +export function planNoneModeQualitySetting( + projection: QualityPluginProjection, + enabled: boolean, + human: VerifiedHumanTurn | null, +): QualitySettingPlan { + if (!SAFE_ID.test(projection.intentUuid)) { + return { ok: false, error: { code: "INTENT_MISMATCH", message: "quality plugin intent is invalid" } }; + } + if (human === null || !human.verified || human.eventType !== "HUMAN_TURN" || !SAFE_ID.test(human.turnId)) { + return { + ok: false, + error: { code: "PROVENANCE_REQUIRED", message: "none-mode quality setting requires a verified human turn" }, + }; + } + return { + ok: true, + projection: { + ...projection, + noneModeOptedIn: enabled, + provenanceTurnId: enabled ? human.turnId : null, + projectionRevision: projection.projectionRevision + 1, + }, + event: { + type: enabled ? "QUALITY_REPAIR_OPTED_IN" : "QUALITY_REPAIR_OPTED_OUT", + intentUuid: projection.intentUuid, + humanTurnId: human.turnId, + }, + }; +} + +export interface QualityEvidenceProviderDescriptor { + readonly id: string; + readonly schemaVersion: 1; + readonly outputSchemaDigest: string; + readonly redactionPolicyId: string; +} + +export interface QualityJudgeInstructionDescriptor { + readonly id: string; + readonly contentDigest: string; + readonly evidenceSchemaDigest: string; +} + +export interface QualityRouteRuleDescriptor { + readonly id: string; + readonly monitorId: string; + readonly routeId: "repair" | "replan" | "repair-stalled"; + readonly instructionId: string; + readonly disposition: "continue" | "latch"; +} + +export interface QualityRequiredOutputDescriptor { + readonly outputId: string; + readonly stageSelector: string; + readonly verifierId: string; + readonly verificationConditionId: string; +} + +export interface NormalizedQualityContribution { + readonly pluginId: string; + readonly schemaVersion: 1; + readonly trusted: true; + readonly contentDigest: string; + readonly monitorManifest: unknown; + readonly loopContributions: LoopMonitorContributions; + readonly evidenceProviders: readonly QualityEvidenceProviderDescriptor[]; + readonly judgeInstructions: readonly QualityJudgeInstructionDescriptor[]; + readonly routeRules: readonly QualityRouteRuleDescriptor[]; + readonly requiredOutputs: readonly QualityRequiredOutputDescriptor[]; +} + +function qualityContributionContent(threshold: number) { + const monitorId = "quality-repair"; + const providerId = "quality-evidence-v1"; + const instructionId = "quality-judge-v1"; + const monitorManifest = { + loopMonitors: [{ + id: monitorId, + cycle: ["QUALITY_CHECK", "QUALITY_NON_PROGRESS"], + ignoreEvents: [], + threshold, + evidenceProviderId: providerId, + judgeInstructionId: instructionId, + routes: ["repair", "replan", "repair-stalled"], + transitionTable: { + QUALITY_CHECK: ["QUALITY_NON_PROGRESS", "QUALITY_STRICT_PROGRESS"], + QUALITY_NON_PROGRESS: ["QUALITY_CHECK", "QUALITY_STRICT_PROGRESS"], + QUALITY_STRICT_PROGRESS: ["QUALITY_CHECK", "QUALITY_STRICT_PROGRESS"], + }, + }], + runtimeLimits: { maxPendingDeliveries: threshold + 1 }, + }; + const loopContributions: LoopMonitorContributions = { + evidenceProviders: [{ id: providerId }], + judgeInstructions: [{ id: instructionId }], + routes: [ + { id: "repair", kind: "transition", targetEvent: "QUALITY_NON_PROGRESS" }, + { id: "replan", kind: "transition", targetEvent: "QUALITY_NON_PROGRESS" }, + { + id: "repair-stalled", + kind: "park", + reasonCode: "REPAIR_STALLED", + resumeCondition: "quality-any-of-evidence-change-human-retry", + }, + ], + }; + const evidenceProviders: readonly QualityEvidenceProviderDescriptor[] = [{ + id: providerId, + schemaVersion: 1, + outputSchemaDigest: digest("quality-evidence-batch-v1"), + redactionPolicyId: "quality-safe-metadata-v1", + }]; + const judgeInstructions: readonly QualityJudgeInstructionDescriptor[] = [{ + id: instructionId, + contentDigest: digest("quality-replan-first-v1"), + evidenceSchemaDigest: evidenceProviders[0]!.outputSchemaDigest, + }]; + const routeRules: readonly QualityRouteRuleDescriptor[] = [ + { id: "quality-route-repair", monitorId, routeId: "repair", instructionId, disposition: "continue" }, + { id: "quality-route-replan", monitorId, routeId: "replan", instructionId, disposition: "continue" }, + { + id: "quality-route-repair-stalled", + monitorId, + routeId: "repair-stalled", + instructionId, + disposition: "latch", + }, + ]; + return { + pluginId: "amadeus.quality-repair.first-party", + schemaVersion: 1 as const, + trusted: true as const, + monitorManifest, + loopContributions, + evidenceProviders, + judgeInstructions, + routeRules, + requiredOutputs: [] as readonly QualityRequiredOutputDescriptor[], + }; +} + +export function createFirstPartyQualityContribution(threshold: number): NormalizedQualityContribution { + if (!Number.isInteger(threshold) || threshold <= 0) { + throw new Error("quality-repair-threshold-must-be-positive"); + } + const content = qualityContributionContent(threshold); + return { ...content, contentDigest: digest(content) }; +} + +export type QualityPluginActivation = + | { + readonly kind: "active"; + readonly reason: "none-human-opt-in" | "mode-required"; + readonly contribution: NormalizedQualityContribution; + readonly graph: CompiledLoopMonitorGraph; + } + | { readonly kind: "disabled"; readonly reason: "none-default-off" } + | { + readonly kind: "error"; + readonly error: { readonly code: "ACTIVATION_FAILED"; readonly message: string }; + }; + +function compileQualityContribution( + contribution: NormalizedQualityContribution | null, +): { readonly contribution: NormalizedQualityContribution; readonly graph: CompiledLoopMonitorGraph } | null { + if (contribution === null || contribution.trusted !== true || contribution.schemaVersion !== 1) return null; + const { contentDigest, ...content } = contribution; + if (contentDigest !== digest(content)) return null; + if (contribution.requiredOutputs.length !== 0) return null; + const providerIds = new Set(contribution.evidenceProviders.map((item) => item.id)); + const instructionIds = new Set(contribution.judgeInstructions.map((item) => item.id)); + if (providerIds.size !== contribution.evidenceProviders.length || + instructionIds.size !== contribution.judgeInstructions.length) return null; + for (const rule of contribution.routeRules) { + if (!instructionIds.has(rule.instructionId) || rule.monitorId !== "quality-repair") return null; + if ((rule.routeId === "repair-stalled") !== (rule.disposition === "latch")) return null; + } + const compiled = compileLoopMonitorManifest(contribution.monitorManifest, contribution.loopContributions); + return compiled.ok ? { contribution, graph: compiled.graph } : null; +} + +export function resolveQualityPluginActivation(input: { + readonly mode: QualityAutonomyMode; + readonly projection: QualityPluginProjection; + readonly contribution: NormalizedQualityContribution | null; +}): QualityPluginActivation { + if (input.mode === "none" && !input.projection.noneModeOptedIn) { + return { kind: "disabled", reason: "none-default-off" }; + } + const compiled = compileQualityContribution(input.contribution); + if (compiled === null) { + return { + kind: "error", + error: { code: "ACTIVATION_FAILED", message: "trusted first-party Quality Repair contribution is unavailable" }, + }; + } + return { + kind: "active", + reason: input.mode === "none" ? "none-human-opt-in" : "mode-required", + ...compiled, + }; +} + +export type ReviewerVerdict = "READY" | "NOT-READY" | "NOT READY"; + +export type QualityObservation = + | { + readonly kind: "reviewer"; + readonly invocationId: string; + readonly verifierId: string; + readonly validationReceipt: string | null; + readonly verdict: ReviewerVerdict; + readonly blockers: readonly { + readonly findingId: string; + readonly artifactId: string | null; + readonly failureFingerprint: string; + }[]; + } + | { + readonly kind: "sensor"; + readonly sensorId: string; + readonly blocking: boolean; + readonly status: "passed" | "failed" | "incomplete"; + readonly outputId: string; + readonly terminalReceipt: string | null; + } + | { + readonly kind: "produce"; + readonly outputId: string; + readonly required: boolean; + readonly status: "present" | "missing" | "invalid"; + readonly verifierId: string; + readonly receipt: string | null; + } + | { + readonly kind: "condition"; + readonly conditionKind: "verification" | "completion"; + readonly conditionId: string; + readonly status: "satisfied" | "unsatisfied" | "incomplete"; + readonly verifierId: string; + readonly receipt: string | null; + }; + +export type QualityEvidenceSourceEvent = QualityObservation | { + readonly kind: "human-request-changes"; + readonly turnId: string; +}; + +export function collectQualityObservations( + events: readonly QualityEvidenceSourceEvent[], +): readonly QualityObservation[] { + return events.filter((event): event is QualityObservation => event.kind !== "human-request-changes"); +} + +export type QualitySourceCategory = "reviewer" | "sensor" | "produce" | "verification" | "completion"; +export type QualityFailureKind = + | "blocker" + | "failed" + | "incomplete" + | "missing" + | "invalid" + | "unmet" + | "evidence-incomplete"; + +export interface QualityObligation { + readonly obligationId: string; + readonly sourceCategory: QualitySourceCategory; + readonly failureKind: QualityFailureKind; + readonly stageInstanceId: string; + readonly boltId: string; + readonly artifactId: string | null; + readonly verifierId: string; + readonly failureFingerprint: string; + readonly status: "unresolved"; +} + +export interface QualityVerifierSuccessReceipt { + readonly obligationId: string; + readonly verifierId: string; + readonly receiptDigest: string; +} + +export interface QualityEvidenceSnapshot { + readonly intentUuid: string; + readonly monitorId: string; + readonly stageInstanceId: string; + readonly boltId: string; + readonly graphRevision: string; + readonly qualityScopeId: string; + readonly epochStartEventIdentity: string; + readonly qualityEpochId: string; + readonly unresolved: readonly QualityObligation[]; + readonly resolvedIds: readonly string[]; + readonly addedIds: readonly string[]; + readonly retainedIds: readonly string[]; + readonly snapshotFingerprint: string; + readonly verifierSuccessReceipts: readonly QualityVerifierSuccessReceipt[]; +} + +export interface QualityEvidenceBatchInput { + readonly providerId: string; + readonly intentUuid: string; + readonly monitorId: string; + readonly stageInstanceId: string; + readonly boltId: string; + readonly graphRevision: string; + readonly epochStartEventIdentity?: string; + readonly previousSnapshot: QualityEvidenceSnapshot | null; + readonly observations: readonly QualityObservation[]; +} + +export type QualityEvidenceResult = + | { readonly ok: true; readonly snapshot: QualityEvidenceSnapshot } + | { + readonly ok: false; + readonly error: { + readonly code: "INCOMPLETE" | "SCOPE_MISMATCH"; + readonly message: string; + }; + }; + +function validId(value: string): boolean { + return SAFE_ID.test(value); +} + +function validDigest(value: string | null): value is string { + return value !== null && SHA256.test(value); +} + +function obligationIdOf( + sourceCategory: QualitySourceCategory, + stageInstanceId: string, + identity: unknown, +): string { + return stableId("quality-obligation", [sourceCategory, stageInstanceId, identity]); +} + +function obligation( + input: QualityEvidenceBatchInput, + sourceCategory: QualitySourceCategory, + failureKind: QualityFailureKind, + identity: unknown, + artifactId: string | null, + verifierId: string, + failureFingerprint: string, +): QualityObligation { + return { + obligationId: obligationIdOf(sourceCategory, input.stageInstanceId, identity), + sourceCategory, + failureKind, + stageInstanceId: input.stageInstanceId, + boltId: input.boltId, + artifactId, + verifierId, + failureFingerprint, + status: "unresolved", + }; +} + +function normalizeObservation( + input: QualityEvidenceBatchInput, + observation: QualityObservation, +): { readonly obligations: readonly QualityObligation[]; readonly successes: readonly QualityVerifierSuccessReceipt[] } { + if (observation.kind === "reviewer") { + if (!validId(observation.invocationId) || !validId(observation.verifierId)) { + throw new Error("reviewer-identity-incomplete"); + } + if (observation.verdict === "READY") { + if (observation.blockers.length === 0) return { obligations: [], successes: [] }; + throw new Error("reviewer-verdict-conflicts-with-blockers"); + } + if (!validDigest(observation.validationReceipt) || observation.blockers.length === 0) { + return { + obligations: [obligation( + input, + "reviewer", + "evidence-incomplete", + observation.invocationId, + null, + observation.verifierId, + digest([observation.invocationId, "evidence-incomplete"]), + )], + successes: [], + }; + } + return { + obligations: observation.blockers.map((blocker) => obligation( + input, + "reviewer", + "blocker", + [observation.invocationId, blocker.findingId], + blocker.artifactId, + observation.verifierId, + SHA256.test(blocker.failureFingerprint) + ? blocker.failureFingerprint + : digest([observation.invocationId, blocker.findingId, "evidence-incomplete"]), + )), + successes: [], + }; + } + if (observation.kind === "sensor") { + if (!validId(observation.sensorId) || !validId(observation.outputId)) { + throw new Error("sensor-identity-incomplete"); + } + if (!observation.blocking) return { obligations: [], successes: [] }; + if (observation.status === "passed") { + const obligationId = obligationIdOf("sensor", input.stageInstanceId, [observation.sensorId, observation.outputId]); + return { + obligations: [], + successes: validDigest(observation.terminalReceipt) + ? [{ obligationId, verifierId: observation.sensorId, receiptDigest: observation.terminalReceipt }] + : [], + }; + } + const incomplete = observation.status === "incomplete" || !validDigest(observation.terminalReceipt); + return { + obligations: [obligation( + input, + "sensor", + incomplete ? "evidence-incomplete" : "failed", + [observation.sensorId, observation.outputId], + observation.outputId, + observation.sensorId, + digest([observation.sensorId, observation.outputId, observation.status, observation.terminalReceipt]), + )], + successes: [], + }; + } + if (observation.kind === "produce") { + if (!validId(observation.outputId) || !validId(observation.verifierId)) { + throw new Error("produce-identity-incomplete"); + } + if (!observation.required) return { obligations: [], successes: [] }; + if (observation.status === "present") { + const obligationId = obligationIdOf("produce", input.stageInstanceId, observation.outputId); + return { + obligations: [], + successes: validDigest(observation.receipt) + ? [{ obligationId, verifierId: observation.verifierId, receiptDigest: observation.receipt }] + : [], + }; + } + return { + obligations: [obligation( + input, + "produce", + observation.status, + observation.outputId, + observation.outputId, + observation.verifierId, + digest([observation.outputId, observation.status, observation.receipt]), + )], + successes: [], + }; + } + if (!validId(observation.conditionId) || !validId(observation.verifierId)) { + throw new Error("condition-identity-incomplete"); + } + const category = observation.conditionKind; + if (observation.status === "satisfied") { + const obligationId = obligationIdOf(category, input.stageInstanceId, observation.conditionId); + return { + obligations: [], + successes: validDigest(observation.receipt) + ? [{ obligationId, verifierId: observation.verifierId, receiptDigest: observation.receipt }] + : [], + }; + } + return { + obligations: [obligation( + input, + category, + observation.status === "incomplete" || !validDigest(observation.receipt) + ? "evidence-incomplete" + : "unmet", + observation.conditionId, + null, + observation.verifierId, + digest([category, observation.conditionId, observation.status, observation.receipt]), + )], + successes: [], + }; +} + +function sameSnapshotScope(input: QualityEvidenceBatchInput, previous: QualityEvidenceSnapshot): boolean { + return input.intentUuid === previous.intentUuid && input.monitorId === previous.monitorId && + input.stageInstanceId === previous.stageInstanceId && input.boltId === previous.boltId && + input.graphRevision === previous.graphRevision; +} + +export function normalizeQualityEvidence(input: QualityEvidenceBatchInput): QualityEvidenceResult { + if (![input.providerId, input.intentUuid, input.monitorId, input.stageInstanceId, input.boltId].every(validId) || + input.observations.length === 0) { + return { ok: false, error: { code: "INCOMPLETE", message: "quality evidence batch identity or observations are incomplete" } }; + } + if (input.previousSnapshot !== null && !sameSnapshotScope(input, input.previousSnapshot)) { + return { ok: false, error: { code: "SCOPE_MISMATCH", message: "previous quality snapshot belongs to another scope" } }; + } + const qualityScopeId = stableId("quality-scope", [ + input.intentUuid, + input.monitorId, + input.stageInstanceId, + input.boltId, + input.graphRevision, + ]); + const epochStartEventIdentity = input.epochStartEventIdentity ?? + input.previousSnapshot?.epochStartEventIdentity ?? stableId("quality-epoch-start", [qualityScopeId, "genesis"]); + const qualityEpochId = stableId("quality-epoch", [qualityScopeId, epochStartEventIdentity]); + if (input.previousSnapshot !== null && input.previousSnapshot.qualityEpochId !== qualityEpochId) { + return { ok: false, error: { code: "SCOPE_MISMATCH", message: "previous quality snapshot belongs to another epoch" } }; + } + try { + const parsed = input.observations.map((item) => normalizeObservation(input, item)); + const byId = new Map(); + for (const item of parsed.flatMap((entry) => entry.obligations)) { + const prior = byId.get(item.obligationId); + if (prior !== undefined && canonicalJson(prior) !== canonicalJson(item)) { + return { ok: false, error: { code: "INCOMPLETE", message: "conflicting duplicate quality obligation" } }; + } + byId.set(item.obligationId, item); + } + const unresolved = [...byId.values()].sort((a, b) => bytewise(a.obligationId, b.obligationId)); + const currentIds = new Set(unresolved.map((item) => item.obligationId)); + const previousIds = new Set(input.previousSnapshot?.unresolved.map((item) => item.obligationId) ?? []); + const resolvedIds = [...previousIds].filter((id) => !currentIds.has(id)).sort(bytewise); + const addedIds = [...currentIds].filter((id) => !previousIds.has(id)).sort(bytewise); + const retainedIds = [...currentIds].filter((id) => previousIds.has(id)).sort(bytewise); + const verifierSuccessReceipts = parsed.flatMap((entry) => entry.successes) + .sort((a, b) => bytewise(`${a.obligationId}:${a.receiptDigest}`, `${b.obligationId}:${b.receiptDigest}`)); + return { + ok: true, + snapshot: { + intentUuid: input.intentUuid, + monitorId: input.monitorId, + stageInstanceId: input.stageInstanceId, + boltId: input.boltId, + graphRevision: input.graphRevision, + qualityScopeId, + epochStartEventIdentity, + qualityEpochId, + unresolved, + resolvedIds, + addedIds, + retainedIds, + snapshotFingerprint: digest([ + qualityScopeId, + qualityEpochId, + unresolved.map((item) => [item.obligationId, item.sourceCategory, item.failureKind, item.failureFingerprint]), + ]), + verifierSuccessReceipts, + }, + }; + } catch (cause) { + return { + ok: false, + error: { code: "INCOMPLETE", message: cause instanceof Error ? cause.message : String(cause) }, + }; + } +} + +export type QualityNonProgressPattern = "fixed-point" | "churn" | "regression" | "undetermined"; + +export type QualityProgress = + | { readonly kind: "initial"; readonly threshold: number } + | { readonly kind: "collecting"; readonly consecutiveNonProgress: number; readonly threshold: number } + | { readonly kind: "strict-progress"; readonly resolvedIds: readonly string[]; readonly addedIds: readonly string[] } + | { + readonly kind: "threshold"; + readonly consecutiveNonProgress: number; + readonly threshold: number; + readonly pattern: QualityNonProgressPattern; + readonly requiredRoute: "replan" | "repair-stalled"; + }; + +export interface QualityReviewCycle { + readonly reviewCycleId: string; + readonly previousReviewCycleId: string | null; + readonly iteration: 1; + readonly cycleIndex: number; + readonly replanFingerprint: string; +} + +export interface QualityEpochProjection { + readonly qualityScopeId: string; + readonly qualityEpochId: string; + readonly epochStartEventIdentity: string; + readonly threshold: number; + readonly window: readonly QualityEvidenceSnapshot[]; + readonly consecutiveNonProgress: number; + readonly replanSinceLastProgress: boolean; + readonly currentReviewCycle: QualityReviewCycle | null; +} + +export function createQualityEpochProjection( + snapshot: QualityEvidenceSnapshot, + threshold: number, +): QualityEpochProjection { + if (!Number.isInteger(threshold) || threshold <= 0) throw new Error("quality-epoch-invalid-threshold"); + return { + qualityScopeId: snapshot.qualityScopeId, + qualityEpochId: snapshot.qualityEpochId, + epochStartEventIdentity: snapshot.epochStartEventIdentity, + threshold, + window: [], + consecutiveNonProgress: 0, + replanSinceLastProgress: false, + currentReviewCycle: null, + }; +} + +function obligationIds(snapshot: QualityEvidenceSnapshot): Set { + return new Set(snapshot.unresolved.map((item) => item.obligationId)); +} + +function isStrictSubset(current: Set, previous: Set): boolean { + return current.size < previous.size && [...current].every((id) => previous.has(id)); +} + +function isStrictProgress( + previous: QualityEvidenceSnapshot | null, + snapshot: QualityEvidenceSnapshot, +): boolean { + if (snapshot.unresolved.length === 0) return true; + return previous !== null && + isStrictSubset(obligationIds(snapshot), obligationIds(previous)) && snapshot.addedIds.length === 0; +} + +function classifyPattern( + window: readonly QualityEvidenceSnapshot[], + threshold: number, +): QualityNonProgressPattern { + const latest = window.at(-1); + if (latest === undefined) return "undetermined"; + const earlierIndex = window.slice(0, -1).findLastIndex( + (item) => item.snapshotFingerprint === latest.snapshotFingerprint, + ); + if (earlierIndex >= 0 && window.slice(earlierIndex + 1, -1).some( + (item) => item.snapshotFingerprint !== latest.snapshotFingerprint, + )) return "regression"; + const recent = window.slice(-threshold); + if (recent.length === threshold && recent.every( + (item) => item.snapshotFingerprint === latest.snapshotFingerprint, + )) return "fixed-point"; + if (recent.length === threshold && new Set(recent.map((item) => item.snapshotFingerprint)).size > 1) { + let monotonicallyShrank = true; + for (let index = 1; index < recent.length; index += 1) { + if (!isStrictSubset(obligationIds(recent[index]!), obligationIds(recent[index - 1]!))) { + monotonicallyShrank = false; + break; + } + } + if (!monotonicallyShrank) return "churn"; + } + return "undetermined"; +} + +export interface QualityDeliveryPlan { + readonly progress: QualityProgress; + readonly nextProjection: QualityEpochProjection; + readonly monitorEvent: "QUALITY_CHECK" | "QUALITY_STRICT_PROGRESS"; + readonly routeIds: readonly ("replan" | "repair-stalled")[]; + readonly deterministicAction: "repair" | null; +} + +export function planQualityDelivery( + projection: QualityEpochProjection, + snapshot: QualityEvidenceSnapshot, +): QualityDeliveryPlan { + if (projection.qualityScopeId !== snapshot.qualityScopeId || projection.qualityEpochId !== snapshot.qualityEpochId) { + throw new Error("quality-delivery-scope-mismatch"); + } + const previous = projection.window.at(-1) ?? null; + const strictProgress = isStrictProgress(previous, snapshot); + const window = [...projection.window, snapshot].slice(-(projection.threshold + 1)); + let progress: QualityProgress; + let consecutiveNonProgress = projection.consecutiveNonProgress; + let replanSinceLastProgress = projection.replanSinceLastProgress; + if (strictProgress) { + progress = { + kind: "strict-progress", + resolvedIds: snapshot.resolvedIds, + addedIds: snapshot.addedIds, + }; + consecutiveNonProgress = 0; + replanSinceLastProgress = false; + } else if (previous === null) { + progress = { kind: "initial", threshold: projection.threshold }; + } else { + consecutiveNonProgress += 1; + if (consecutiveNonProgress >= projection.threshold) { + progress = { + kind: "threshold", + consecutiveNonProgress, + threshold: projection.threshold, + pattern: classifyPattern(window, projection.threshold), + requiredRoute: replanSinceLastProgress ? "repair-stalled" : "replan", + }; + } else { + progress = { kind: "collecting", consecutiveNonProgress, threshold: projection.threshold }; + } + } + const nextProjection: QualityEpochProjection = { + ...projection, + window, + consecutiveNonProgress, + replanSinceLastProgress, + }; + return { + progress, + nextProjection, + monitorEvent: progress.kind === "strict-progress" ? "QUALITY_STRICT_PROGRESS" : "QUALITY_CHECK", + routeIds: progress.kind === "threshold" ? [progress.requiredRoute] : [], + deterministicAction: ["threshold", "strict-progress"].includes(progress.kind) ? null : "repair", + }; +} + +export interface QualityReplanReceipt { + readonly judgeInvocationId: string; + readonly planDigest: string; + readonly agentId: string; + readonly contextId: string; +} + +export function recordQualityReplan( + projection: QualityEpochProjection, + receipt: QualityReplanReceipt, +): { readonly replanId: string; readonly nextProjection: QualityEpochProjection } { + if (![receipt.judgeInvocationId, receipt.agentId, receipt.contextId].every(validId) || !SHA256.test(receipt.planDigest)) { + throw new Error("quality-replan-receipt-invalid"); + } + const cycleIndex = (projection.currentReviewCycle?.cycleIndex ?? -1) + 1; + const reviewCycleId = stableId("quality-review-cycle", [ + projection.qualityEpochId, + receipt.judgeInvocationId, + receipt.planDigest, + cycleIndex, + ]); + return { + replanId: stableId("quality-replan", [ + projection.qualityEpochId, + projection.window.at(-1)?.snapshotFingerprint, + receipt.judgeInvocationId, + ]), + nextProjection: { + ...projection, + consecutiveNonProgress: 0, + replanSinceLastProgress: true, + currentReviewCycle: { + reviewCycleId, + previousReviewCycleId: projection.currentReviewCycle?.reviewCycleId ?? null, + iteration: 1, + cycleIndex, + replanFingerprint: receipt.planDigest, + }, + }, + }; +} + +export function qualityDigest(value: unknown): string { + return digest(value); +} + +export function qualityStableId(namespace: string, value: unknown): string { + return stableId(namespace, value); +} diff --git a/tests/.coverage-patch-allowlist.json b/tests/.coverage-patch-allowlist.json index 5e0af29f9..b1883bf4c 100644 --- a/tests/.coverage-patch-allowlist.json +++ b/tests/.coverage-patch-allowlist.json @@ -4591,5 +4591,93 @@ }, "reason": "This is the credential-bearing live Pi RPC journey. It is exercised only by the serial Pi candidate E2E with a real provider and is intentionally outside the smoke/unit/integration coverage run.", "expiry": "remove when coverage includes the live Pi candidate E2E or the live RPC transport becomes injectable" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair.ts", + "selector": { + "function": "resolveQualityPluginActivation", + "fingerprint": "sha256:9cbaae85537e8dc8a026001e0ed28eb538dad402543608a1a7cbc2e2d3b2e497", + "anchorLines": 3, + "targetLines": "1-3" + }, + "reason": "These lines contain only the multiline TypeScript input type. Every activation branch is covered directly by the t428 unit and t429 integration tests; the signature has no runtime statement.", + "expiry": "remove when the patch gate excludes type-only lines or Bun stops emitting DA records for them" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair-runtime.ts", + "selector": { + "function": "createMemoryQualityRepairRepository", + "fingerprint": "sha256:6a770df7bfa06807e51565b1f265a96c9c00e6f3ac10aca0f637bd1dd529f9e0", + "anchorLines": 3, + "targetLines": "1-3" + }, + "reason": "These lines contain only the multiline TypeScript input type. The in-memory repository's transaction and commit paths are covered directly by t429 integration tests; the signature has no runtime statement.", + "expiry": "remove when the patch gate excludes type-only lines or Bun stops emitting DA records for them" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair-runtime.ts", + "selector": { + "function": "createMemoryQualityRepairRepository", + "fingerprint": "sha256:01ade2dfdb20001585531e747b39508dd06735690eeddc50331635fcb5d01812", + "anchorLines": 3, + "targetLines": "1-3" + }, + "reason": "These lines contain only the multiline TypeScript type annotation of the active local binding. This staged-transaction state is exercised directly by t429 integration tests; the annotation has no runtime statement.", + "expiry": "remove when the patch gate excludes type-only lines or Bun stops emitting DA records for them" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair-runtime.ts", + "selector": { + "function": "createQualityRepairCoordinator", + "fingerprint": "sha256:9514bb56c20c53305bc3ce345b4c8113fb6fb9bea62d04d17c0774a6185a348f", + "anchorLines": 2, + "targetLines": "1-2" + }, + "reason": "These lines contain only the multiline TypeScript input type. Coordinator construction and every dispatch/resume/status branch it exposes are covered directly by t429 integration tests; the signature has no runtime statement.", + "expiry": "remove when the patch gate excludes type-only lines or Bun stops emitting DA records for them" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair-runtime.ts", + "selector": { + "function": "rebaseResumeEvidence", + "fingerprint": "sha256:50d05ad16f9dfdce52605e81172dde639c634978c7c8cd6dd623616720fdb824", + "anchorLines": 2, + "targetLines": "1-2" + }, + "reason": "These lines contain only the multiline TypeScript return type annotation. Every resume-rebase branch is covered directly by t429 integration tests; the annotation has no runtime statement.", + "expiry": "remove when the patch gate excludes type-only lines or Bun stops emitting DA records for them" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair-runtime.ts", + "selector": { + "function": "rebaseResumeEvidence", + "fingerprint": "sha256:4375b264eea62aa8a742ba1c11ab24e413c29a11b541273ff7f6d679780579af", + "anchorLines": 5, + "targetLines": "1-5" + }, + "reason": "This is a defensive fail-closed propagation branch, not dead code by oversight. resolveResumeBasis already normalizes the identical evidence input (same identity fields and observations) before rebaseResumeEvidence runs, and the rebase re-normalization only strips previousSnapshot-dependent failure modes (scope/epoch mismatch), so a second normalizeQualityEvidence failure cannot occur today. The branch is kept so that if normalizeQualityEvidence grows a new failure mode in the future, the resume flow fails closed instead of silently resuming.", + "expiry": "remove if rebaseResumeEvidence gains a reachable failure mode or the resume flow stops re-normalizing evidence" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair-replay.ts", + "selector": { + "function": "createAuditQualityRepairRepository", + "fingerprint": "sha256:32a5766c6bde07c0f6a4c0e2707acf84def94a5aa25333d9e6902654268002e8", + "anchorLines": 3, + "targetLines": "1-3" + }, + "reason": "These lines contain only the multiline TypeScript input type. The audit repository's commit, reload, and replay paths are covered directly by t429 integration tests; the signature has no runtime statement.", + "expiry": "remove when the patch gate excludes type-only lines or Bun stops emitting DA records for them" + }, + { + "file": "packages/framework/core/tools/amadeus-quality-repair-replay.ts", + "selector": { + "function": "replayQualityRepairScope", + "fingerprint": "sha256:5eecfbafe54e32327197aee81a425fbfcb7b5b5675a44d508afb37d385f927ae", + "anchorLines": 4, + "targetLines": "1-4" + }, + "reason": "These lines contain only the multiline TypeScript return type annotation. Scope replay including the not-found rejection is covered directly by t429 integration tests; the annotation has no runtime statement.", + "expiry": "remove when the patch gate excludes type-only lines or Bun stops emitting DA records for them" } ] diff --git a/tests/.coverage-ratchet.json b/tests/.coverage-ratchet.json index d2523172d..3e55ee1b0 100644 --- a/tests/.coverage-ratchet.json +++ b/tests/.coverage-ratchet.json @@ -2,7 +2,7 @@ "note": "Committed baseline: covered-unit count per class. The --check ratchet fails CI if any class's covered count DROPS below these numbers without a reviewed deferred entry. Monotonic anti-regression: you can cover more, never silently less. Regenerate with: bun tests/gen-coverage-registry.ts", "coveredByClass": { "function": 177, - "audit": 40, + "audit": 41, "scope": 15, "stage": 8, "hook": 14, diff --git a/tests/.coverage-registry.json b/tests/.coverage-registry.json index e751a60e1..97e351a40 100644 --- a/tests/.coverage-registry.json +++ b/tests/.coverage-registry.json @@ -20,10 +20,10 @@ "render-surface": "tui" }, "counts": { - "total": 600, + "total": 601, "enumeratedByClass": { "function": 344, - "audit": 86, + "audit": 87, "scope": 15, "stage": 32, "hook": 14, @@ -32,7 +32,7 @@ }, "coveredByClass": { "function": 177, - "audit": 40, + "audit": 41, "scope": 15, "stage": 8, "hook": 14, @@ -451,6 +451,18 @@ "coveredBy": [], "status": "UNCOVERED" }, + { + "unitClass": "audit", + "unitId": "QUALITY_REPAIR_TRANSACTION_COMMITTED", + "minMechanism": "none", + "coveredBy": [ + { + "file": "tests/integration/t429-quality-repair-runtime.integration.test.ts", + "mechanism": "none" + } + ], + "status": "covered" + }, { "unitClass": "audit", "unitId": "QUESTION_ANSWERED", diff --git a/tests/integration/event-registry-drift.test.ts b/tests/integration/event-registry-drift.test.ts index f0437fe9d..c7e2dcee2 100644 --- a/tests/integration/event-registry-drift.test.ts +++ b/tests/integration/event-registry-drift.test.ts @@ -47,11 +47,11 @@ describe("registry internal consistency (VER-1)", () => { expect(() => assertRegistryConsistent()).not.toThrow(); }); - test("canonical cardinality is pinned at 86 (#1672, #1602, #1919, #2095) — vacuous equality banned", () => { - expect(EXPECTED_CANONICAL_COUNT).toBe(86); - expect(canonicalAuditEvents().length).toBe(86); - expect(SETS.registryCanonical.size).toBe(86); - expect(SETS.auditVocabulary.size).toBe(86); + test("canonical cardinality is pinned at 87 (#1672, #1602, #1919, #2095, #2096) — vacuous equality banned", () => { + expect(EXPECTED_CANONICAL_COUNT).toBe(87); + expect(canonicalAuditEvents().length).toBe(87); + expect(SETS.registryCanonical.size).toBe(87); + expect(SETS.auditVocabulary.size).toBe(87); }); test("canonical defs always map to the audit journal; telemetry defs never do (FR-EXP-4)", () => { @@ -189,7 +189,7 @@ describe("extraction parity with the t28 vocabulary guard", () => { test("extractAuditVocabulary agrees with the t28 sed-range rule on the shipped amadeus-audit.ts", () => { const body = readFileSync(join(AMADEUS_SRC, "tools", "amadeus-audit.ts"), "utf-8"); const vocab = extractAuditVocabulary(body); - expect(vocab.length).toBe(86); + expect(vocab.length).toBe(87); expect(vocab).toEqual([...SETS.auditVocabulary].sort()); }); }); diff --git a/tests/integration/t429-quality-repair-runtime.integration.test.ts b/tests/integration/t429-quality-repair-runtime.integration.test.ts new file mode 100644 index 000000000..baa9556d4 --- /dev/null +++ b/tests/integration/t429-quality-repair-runtime.integration.test.ts @@ -0,0 +1,1007 @@ +// covers: file:packages/framework/core/tools/amadeus-quality-repair-runtime.ts, audit:QUALITY_REPAIR_TRANSACTION_COMMITTED +// size: medium + +import { describe, expect, test } from "bun:test"; +import { mkdirSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; + +import { + cleanupTestProject, + createTestProject, + DEFAULT_RECORD_DIR, + seededAuditShard, + setupIntegrationProject, +} from "../harness/fixtures.ts"; + +import { + createFirstPartyQualityContribution, + emptyQualityPluginProjection, + resolveQualityPluginActivation, + type QualityEvidenceBatchInput, + type QualityEvidenceSnapshot, + type QualityObservation, +} from "../../packages/framework/core/tools/amadeus-quality-repair.ts"; +import { + createMemoryQualityRepairRepository, + createQualityRepairCoordinator, + renderQualityRepairStatus, + type QualityRepairTransaction, + type QualityReplanPort, +} from "../../packages/framework/core/tools/amadeus-quality-repair-runtime.ts"; +import { + createAuditQualityRepairRepository, + decodeQualityRepairTransaction, + readQualityRepairTransactionsFromAudit, + replayQualityRepairScope, +} from "../../packages/framework/core/tools/amadeus-quality-repair-replay.ts"; +import type { + JudgeDispatchRequest, + JudgePort, + VerifiedHumanTurn, +} from "../../packages/framework/core/tools/amadeus-loop-monitor-runtime.ts"; + +const trace = { traceId: "0123456789abcdef0123456789abcdef", spanId: "0123456789abcdef" }; + +function reviewer(blockerIds: readonly string[]): QualityObservation { + return { + kind: "reviewer", + invocationId: "review-1", + verifierId: "architecture-reviewer", + validationReceipt: `sha256:${"a".repeat(64)}`, + verdict: blockerIds.length === 0 ? "READY" : "NOT-READY", + blockers: blockerIds.map((findingId) => ({ + findingId, + artifactId: "code-summary", + failureFingerprint: `sha256:${"b".repeat(64)}`, + })), + }; +} + +function runtime() { + const contribution = createFirstPartyQualityContribution(2); + const activation = resolveQualityPluginActivation({ + mode: "semi", + projection: emptyQualityPluginProjection("intent-1"), + contribution, + }); + if (activation.kind !== "active") throw new Error("expected active contribution"); + const repository = createMemoryQualityRepairRepository(); + return { + activation, + repository, + coordinator: createQualityRepairCoordinator({ activation, repository }), + }; +} + +function batch( + graphRevision: string, + previousSnapshot: QualityEvidenceSnapshot | null, + blockerIds: readonly string[] = ["blocker-a"], +): QualityEvidenceBatchInput { + return { + providerId: "quality-evidence-v1", + intentUuid: "intent-1", + monitorId: "quality-repair", + stageInstanceId: "code-generation-1", + boltId: "bolt-2", + graphRevision, + previousSnapshot, + observations: [reviewer(blockerIds)], + }; +} + +function sensorBatch( + graphRevision: string, + previousSnapshot: QualityEvidenceSnapshot | null, + status: "failed" | "passed", +): QualityEvidenceBatchInput { + return { + providerId: "quality-evidence-v1", + intentUuid: "intent-1", + monitorId: "quality-repair", + stageInstanceId: "sensor-stage-1", + boltId: "sensor-bolt-1", + graphRevision, + previousSnapshot, + observations: [{ + kind: "sensor", + sensorId: "type-check", + blocking: true, + status, + outputId: "typecheck-output", + terminalReceipt: `sha256:${"9".repeat(64)}`, + }], + }; +} + +function mixedSensorBatch( + graphRevision: string, + previousSnapshot: QualityEvidenceSnapshot | null, + includeSecondFailure: boolean, + successReceipt: string, +): QualityEvidenceBatchInput { + const failed = (outputId: string): QualityObservation => ({ + kind: "sensor", + sensorId: "type-check", + blocking: true, + status: "failed", + outputId, + terminalReceipt: `sha256:${"8".repeat(64)}`, + }); + return { + providerId: "quality-evidence-v1", + intentUuid: "intent-1", + monitorId: "quality-repair", + stageInstanceId: "mixed-sensor-stage-1", + boltId: "mixed-sensor-bolt-1", + graphRevision, + previousSnapshot, + observations: [ + failed("typecheck-output"), + ...(includeSecondFailure ? [failed("lint-output")] : []), + { + kind: "sensor", + sensorId: "type-check", + blocking: true, + status: "passed", + outputId: "typecheck-output", + terminalReceipt: successReceipt, + }, + ], + }; +} + +function judgePort(): { readonly port: JudgePort; readonly requests: JudgeDispatchRequest[] } { + const requests: JudgeDispatchRequest[] = []; + return { + requests, + port: { + dispatch(request) { + requests.push(request); + return { + kind: "completed", + result: { + invocationId: request.invocationId, + routeId: request.allowedRouteIds[0]!, + evidenceFingerprint: request.evidenceFingerprint, + constraintFingerprint: request.routeConstraintFingerprint, + trace: request.trace, + }, + }; + }, + reconcile() { + return { kind: "unknown", reason: "not-used" }; + }, + }, + }; +} + +const replanPort: QualityReplanPort = { + dispatch(request) { + return { + kind: "completed", + receipt: { + judgeInvocationId: request.judgeInvocationId, + planDigest: `sha256:${"d".repeat(64)}`, + agentId: "repair-agent", + contextId: "fresh-context-1", + }, + }; + }, + reconcile() { + return { kind: "unknown", reason: "not-used" }; + }, +}; + +function reachFirstThreshold() { + const instance = runtime(); + let previous: QualityEvidenceSnapshot | null = null; + let result: ReturnType | null = null; + for (let index = 0; index < 3; index += 1) { + result = instance.coordinator.recordEvidence(batch(instance.activation.graph.graphRevision, previous), trace); + previous = result.snapshot; + } + if (result?.kind !== "judge-reserved") throw new Error("missing first threshold"); + return { ...instance, previous, threshold: result }; +} + +function reachStalledRuntime() { + const instance = reachFirstThreshold(); + instance.coordinator.dispatchJudge(instance.threshold.permit, judgePort().port, replanPort); + let previous = instance.previous; + let result: ReturnType | null = null; + for (let index = 0; index < 2; index += 1) { + result = instance.coordinator.recordEvidence(batch(instance.activation.graph.graphRevision, previous), trace); + previous = result.snapshot; + } + if (result?.kind !== "judge-reserved") throw new Error("missing stalled threshold"); + const stalled = instance.coordinator.dispatchJudge(result.permit, judgePort().port, replanPort); + if (stalled.kind !== "REPAIR_STALLED") throw new Error("missing stalled latch"); + const status = instance.coordinator.status(result.snapshot.qualityScopeId); + if (status === null) throw new Error("missing stalled status"); + return { ...instance, previous, status }; +} + +describe("Quality Repair production coordinator", () => { + test("rejects an active contribution without a compiled monitor", () => { + const { activation, repository } = runtime(); + expect(() => createQualityRepairCoordinator({ + activation: { ...activation, graph: { ...activation.graph, loopMonitors: [] } }, + repository, + })).toThrow("quality-repair-monitor-missing"); + }); + + test("keeps consecutive clean snapshots active without deterministic repair actions", () => { + const { activation, repository, coordinator } = runtime(); + const first = coordinator.recordEvidence(batch(activation.graph.graphRevision, null, []), trace); + const second = coordinator.recordEvidence(batch(activation.graph.graphRevision, first.snapshot, []), trace); + + expect(first).toMatchObject({ kind: "repair", progress: { kind: "strict-progress" } }); + expect(second).toMatchObject({ + kind: "repair", + progress: { kind: "strict-progress" }, + projection: { consecutiveNonProgress: 0 }, + }); + expect(repository.readTransactions().flatMap((transaction) => transaction.loopEventSets)).toHaveLength(2); + }); + + test("does not Judge at T-1, replans at first T, and latches at post-replan T", () => { + const { activation, repository, coordinator } = runtime(); + let previous: QualityEvidenceSnapshot | null = null; + + const first = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + expect(first.kind).toBe("repair"); + previous = first.snapshot; + const second = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + expect(second.kind).toBe("repair"); + previous = second.snapshot; + const third = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + expect(third.kind).toBe("judge-reserved"); + previous = third.snapshot; + + const firstJudge = judgePort(); + if (third.kind !== "judge-reserved") throw new Error("missing first threshold"); + const replanned = coordinator.dispatchJudge(third.permit, firstJudge.port, replanPort); + expect(replanned.kind).toBe("replanned"); + expect(firstJudge.requests).toHaveLength(1); + expect(firstJudge.requests[0]?.allowedRouteIds).toEqual(["replan"]); + + const fourth = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + expect(fourth.kind).toBe("repair"); + previous = fourth.snapshot; + const fifth = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + expect(fifth.kind).toBe("judge-reserved"); + previous = fifth.snapshot; + if (fifth.kind !== "judge-reserved") throw new Error("missing stalled threshold"); + const secondJudge = judgePort(); + const stalled = coordinator.dispatchJudge(fifth.permit, secondJudge.port, replanPort); + expect(stalled.kind).toBe("REPAIR_STALLED"); + expect(secondJudge.requests[0]?.allowedRouteIds).toEqual(["repair-stalled"]); + + const status = coordinator.status(fifth.snapshot.qualityScopeId); + expect(status).toMatchObject({ + outcome: "parked", + stopReason: "REPAIR_STALLED", + workflowExecutionState: "suspended", + threshold: 2, + replanSinceLastProgress: true, + }); + expect(status?.resumeCondition.alternatives.map((item) => item.kind)).toEqual([ + "evidence-change", + "human-retry", + ]); + expect(renderQualityRepairStatus(status!)).toContain("REPAIR_STALLED"); + + const decodedEventTypes = repository.readTransactions().flatMap((transaction) => + decodeQualityRepairTransaction(JSON.stringify(transaction)).qualityEvents.map((event) => event.type) + ); + expect(decodedEventTypes).toContain("QUALITY_REPLAN_RESERVED"); + expect(decodedEventTypes).toContain("QUALITY_REPLAN_RECORDED"); + expect(decodedEventTypes).toContain("REPAIR_STALLED"); + + const before = repository.readTransactions().length; + expect(coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace).kind).toBe("REPAIR_STALLED"); + expect(repository.readTransactions()).toHaveLength(before); + }); + + test("atomically resumes a new epoch with the exact human alternative and replays across coordinators", () => { + const { activation, repository, coordinator } = runtime(); + let previous: QualityEvidenceSnapshot | null = null; + const snapshots: ReturnType[] = []; + for (let index = 0; index < 3; index += 1) { + const result = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + previous = result.snapshot; + snapshots.push(result); + } + const firstJudge = judgePort(); + const firstThreshold = snapshots[2]!; + if (firstThreshold.kind !== "judge-reserved") throw new Error("missing first threshold"); + coordinator.dispatchJudge(firstThreshold.permit, firstJudge.port, replanPort); + for (let index = 0; index < 2; index += 1) { + const result = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + previous = result.snapshot; + snapshots.push(result); + } + const stalledThreshold = snapshots.at(-1)!; + if (stalledThreshold.kind !== "judge-reserved") throw new Error("missing stalled threshold"); + coordinator.dispatchJudge(stalledThreshold.permit, judgePort().port, replanPort); + const status = coordinator.status(stalledThreshold.snapshot.qualityScopeId)!; + const humanAlternative = status.resumeCondition.alternatives.find((item) => item.kind === "human-retry")!; + + expect(coordinator.resume({ + qualityScopeId: status.qualityScopeId, + alternativeIdentity: "wrong-alternative", + humanRetry: { verified: true, eventType: "HUMAN_TURN", actor: "human", turnId: "turn-1" }, + })).toMatchObject({ kind: "CONFLICT" }); + const resumed = coordinator.resume({ + qualityScopeId: status.qualityScopeId, + alternativeIdentity: humanAlternative.identity, + humanRetry: { verified: true, eventType: "HUMAN_TURN", actor: "human", turnId: "turn-1" }, + }); + expect(resumed.kind).toBe("resumed"); + if (resumed.kind !== "resumed") throw new Error("missing resumed projection"); + expect(resumed.projection.qualityEpochId).not.toBe(status.qualityEpochId); + expect(resumed.projection.window).toEqual([]); + + const transaction = repository.readTransactions().at(-1)!; + expect(transaction.qualityEvents.map((event) => event.type)).toContain("QUALITY_EPOCH_STARTED"); + expect(transaction.loopEventSets.flatMap((set) => set.events.map((event) => event.type))).toEqual([ + "LOOP_LATCH_CLEARED", + "WORKFLOW_UNPARKED", + ]); + const decodedEpochStart = decodeQualityRepairTransaction(JSON.stringify(transaction)); + expect(decodedEpochStart.qualityEvents).toMatchObject([{ + type: "QUALITY_EPOCH_STARTED", + priorQualityEpochId: status.qualityEpochId, + satisfiedAlternativeIdentity: humanAlternative.identity, + }]); + const replayed = createQualityRepairCoordinator({ activation, repository }); + expect(replayed.status(status.qualityScopeId)?.qualityEpochId).toBe(resumed.projection.qualityEpochId); + }); + + test("reconciles before one attested no-effect successor and never admits attempt 2", () => { + const { activation, repository, coordinator } = runtime(); + let previous: QualityEvidenceSnapshot | null = null; + let thresholdResult: ReturnType | null = null; + for (let index = 0; index < 3; index += 1) { + thresholdResult = coordinator.recordEvidence(batch(activation.graph.graphRevision, previous), trace); + previous = thresholdResult.snapshot; + } + if (thresholdResult?.kind !== "judge-reserved") throw new Error("missing threshold"); + const dispatchAttempts: number[] = []; + const reconcileAttempts: number[] = []; + const recoveryPort: QualityReplanPort = { + dispatch(request) { + dispatchAttempts.push(request.attemptNo); + if (request.attemptNo === 0) return { kind: "accepted", operationRef: "operation-0" }; + return { + kind: "completed", + receipt: { + judgeInvocationId: request.judgeInvocationId, + planDigest: `sha256:${"f".repeat(64)}`, + agentId: "repair-agent", + contextId: "fresh-context-2", + }, + }; + }, + reconcile(request) { + reconcileAttempts.push(request.attemptNo); + return { kind: "no-effect-confirmed", attestationId: "no-effect-0" }; + }, + }; + expect(coordinator.dispatchJudge(thresholdResult.permit, judgePort().port, recoveryPort).kind).toBe( + "replan-pending", + ); + expect(coordinator.resumeReplan(thresholdResult.snapshot.qualityScopeId, recoveryPort).kind).toBe("replanned"); + expect(dispatchAttempts).toEqual([0, 1]); + expect(reconcileAttempts).toEqual([0]); + expect(repository.readTransactions().flatMap((transaction) => transaction.qualityEvents) + .filter((event) => event.type === "QUALITY_REPLAN_RESERVED") + .map((event) => event.type === "QUALITY_REPLAN_RESERVED" ? event.attemptNo : -1)).toEqual([0, 1]); + }); + + test("round-trips canonical transactions into a fresh replay projection", () => { + const { activation, repository, coordinator } = runtime(); + const observed = coordinator.recordEvidence(batch(activation.graph.graphRevision, null), trace); + const encoded = JSON.stringify(repository.readTransactions()[0]); + const decoded = decodeQualityRepairTransaction(encoded); + const restored = createMemoryQualityRepairRepository({ initialTransactions: [decoded] }); + const replay = replayQualityRepairScope(restored, observed.snapshot.qualityScopeId); + expect(replay.projection.latestSnapshot?.snapshotFingerprint).toBe(observed.snapshot.snapshotFingerprint); + expect(replay.status.outcome).toBe("active"); + expect(replay.transactionCount).toBe(1); + expect(() => decodeQualityRepairTransaction('{"schemaVersion":1}')).toThrow( + "invalid-quality-repair-transaction", + ); + const malformed = JSON.parse(encoded) as Record; + malformed.qualityEvents = [{ type: "QUALITY_SNAPSHOT_OBSERVED", projection: {} }]; + expect(() => decodeQualityRepairTransaction(JSON.stringify(malformed))).toThrow( + "invalid-quality-repair-transaction", + ); + }); + + test("does not advance quality projection when the loop delivery is incomplete", () => { + const contribution = createFirstPartyQualityContribution(2); + const activation = resolveQualityPluginActivation({ + mode: "semi", + projection: emptyQualityPluginProjection("intent-1"), + contribution, + }); + if (activation.kind !== "active") throw new Error("expected active contribution"); + const base = createMemoryQualityRepairRepository(); + const repository = { + ...base, + loopRepository: { + ...base.loopRepository, + transaction(): never { + throw new Error("injected-loop-write-failure"); + }, + }, + }; + const coordinator = createQualityRepairCoordinator({ activation, repository }); + const result = coordinator.recordEvidence(batch(activation.graph.graphRevision, null), trace); + expect(result).toMatchObject({ kind: "INCOMPLETE" }); + expect(repository.readTransactions()).toEqual([]); + }); + + test("fails closed when threshold reservation or its deterministic action is incomplete", () => { + const thresholdActivation = resolveQualityPluginActivation({ + mode: "semi", + projection: emptyQualityPluginProjection("intent-1"), + contribution: createFirstPartyQualityContribution(2), + }); + if (thresholdActivation.kind !== "active") throw new Error("expected active contribution"); + const thresholdBase = createMemoryQualityRepairRepository(); + let rejectThreshold = false; + const thresholdRepository = { + ...thresholdBase, + loopRepository: { + ...thresholdBase.loopRepository, + transaction: ((partition, body) => { + if (rejectThreshold) throw new Error("injected-threshold-write-failure"); + return thresholdBase.loopRepository.transaction(partition, body); + }) as typeof thresholdBase.loopRepository.transaction, + }, + }; + const thresholdCoordinator = createQualityRepairCoordinator({ + activation: thresholdActivation, + repository: thresholdRepository, + }); + const first = thresholdCoordinator.recordEvidence(batch(thresholdActivation.graph.graphRevision, null), trace); + const second = thresholdCoordinator.recordEvidence( + batch(thresholdActivation.graph.graphRevision, first.snapshot), + trace, + ); + rejectThreshold = true; + expect(thresholdCoordinator.recordEvidence( + batch(thresholdActivation.graph.graphRevision, second.snapshot), + trace, + )).toMatchObject({ + kind: "INCOMPLETE", + reason: "quality-threshold-did-not-reserve-judge", + }); + + const actionBase = createMemoryQualityRepairRepository(); + let transactionCount = 0; + const actionRepository = { + ...actionBase, + loopRepository: { + ...actionBase.loopRepository, + transaction: ((partition, body) => { + transactionCount += 1; + if (transactionCount === 2) throw new Error("injected-action-write-failure"); + return actionBase.loopRepository.transaction(partition, body); + }) as typeof actionBase.loopRepository.transaction, + }, + }; + const actionCoordinator = createQualityRepairCoordinator({ + activation: thresholdActivation, + repository: actionRepository, + }); + expect(actionCoordinator.recordEvidence(batch(thresholdActivation.graph.graphRevision, null), trace)).toMatchObject({ + kind: "INCOMPLETE", + reason: "canonical-write-failed:injected-action-write-failure", + }); + expect(actionRepository.readTransactions()).toHaveLength(1); + expect(actionRepository.readTransactions()[0]?.qualityEvents).toEqual([]); + }); + + test("rejects conflicting repository history and preserves transaction boundaries", () => { + const { activation, repository, coordinator } = runtime(); + const observed = coordinator.recordEvidence(batch(activation.graph.graphRevision, null), trace); + const canonical = repository.readTransactions()[0]!; + + const duplicateHistory = createMemoryQualityRepairRepository({ initialTransactions: [canonical, canonical] }); + expect(duplicateHistory.readProjection(observed.snapshot.qualityScopeId)?.latestSnapshot?.snapshotFingerprint).toBe( + observed.snapshot.snapshotFingerprint, + ); + + const conflictingTransaction: QualityRepairTransaction = { ...canonical, qualityEvents: [] }; + const conflictingHistory = createMemoryQualityRepairRepository({ + initialTransactions: [canonical, conflictingTransaction], + }); + expect(() => conflictingHistory.readProjection(observed.snapshot.qualityScopeId)).toThrow( + "quality-repair-transaction-conflict", + ); + + const wrongScopeTransaction: QualityRepairTransaction = { ...canonical, qualityScopeId: "wrong-scope" }; + const wrongScopeHistory = createMemoryQualityRepairRepository({ initialTransactions: [wrongScopeTransaction] }); + expect(() => wrongScopeHistory.readProjection("wrong-scope")).toThrow( + "quality-repair-projection-scope-mismatch", + ); + + expect(repository.transaction("outer-scope", () => + repository.transaction("outer-scope", () => "same-scope"))).toBe("same-scope"); + expect(() => repository.transaction("outer-scope", () => + repository.transaction("inner-scope", () => "unreachable"))).toThrow( + "quality-repair-nested-scope-mismatch", + ); + + const projection = repository.readProjection(observed.snapshot.qualityScopeId)!; + expect(repository.readProjectionByPartition(projection.partition)?.qualityScopeId).toBe( + observed.snapshot.qualityScopeId, + ); + const committedSet = canonical.loopEventSets[0]!; + const before = repository.readTransactions().length; + expect(repository.loopRepository.transaction(projection.partition, () => "empty")).toBe("empty"); + expect(repository.loopRepository.transaction(projection.partition, () => undefined)).toBeUndefined(); + expect(repository.readTransactions()).toHaveLength(before); + expect(repository.loopRepository.transaction(projection.partition, (_sets, append) => append(committedSet))).toMatchObject({ + eventSetId: committedSet.eventSetId, + }); + expect(() => repository.loopRepository.transaction(projection.partition, (_sets, append) => + append({ ...committedSet, payloadFingerprint: `sha256:${"c".repeat(64)}` }))).toThrow( + "quality-repair-loop-event-set-conflict", + ); + expect(() => repository.loopRepository.transaction(projection.partition, (_sets, append) => + append({ ...committedSet, partitionKey: "wrong-partition" }))).toThrow( + "quality-repair-loop-partition-mismatch", + ); + + const committedByHook: QualityRepairTransaction[] = []; + let lockEntered = false; + const hookedRepository = createMemoryQualityRepairRepository({ + initialTransactions: [], + onCommit: (transaction) => committedByHook.push(transaction), + transactionLock: (body) => { + lockEntered = true; + return body(); + }, + }); + hookedRepository.transaction(canonical.qualityScopeId, (append) => { + for (const event of canonical.qualityEvents) append(event); + }); + expect(lockEntered).toBe(true); + expect(committedByHook).toHaveLength(1); + expect(hookedRepository.readProjection(canonical.qualityScopeId)?.latestSnapshot).toEqual(observed.snapshot); + }); + + test("fails closed for stale and incomplete evidence without advancing durable state", () => { + const { activation, repository, coordinator } = runtime(); + const first = coordinator.recordEvidence(batch(activation.graph.graphRevision, null), trace); + const before = repository.readTransactions().length; + const stale = { + ...first.snapshot, + snapshotFingerprint: `sha256:${"0".repeat(64)}`, + }; + + expect(coordinator.recordEvidence(batch(activation.graph.graphRevision, stale), trace)).toMatchObject({ + kind: "CONFLICT", + reason: "quality-previous-snapshot-not-current", + snapshot: first.snapshot, + }); + expect(() => coordinator.recordEvidence({ + ...batch(activation.graph.graphRevision, null), + observations: [], + }, trace)).toThrow("quality-evidence-INCOMPLETE"); + expect(repository.readTransactions()).toHaveLength(before); + }); + + test("surfaces non-started Judge and bounded replan outcomes", () => { + const pendingJudge = reachFirstThreshold(); + const notStartedPort: JudgePort = { + dispatch: () => ({ kind: "not-started", reason: "judge-unavailable" }), + reconcile: () => ({ kind: "unknown", reason: "not-used" }), + }; + expect(pendingJudge.coordinator.dispatchJudge( + pendingJudge.threshold.permit, + notStartedPort, + replanPort, + )).toEqual({ kind: "CONFLICT", reason: "quality-judge-route-not-replan" }); + + const uncertainReplan = reachFirstThreshold(); + const effectPossiblePort: QualityReplanPort = { + dispatch: () => ({ kind: "effect-possible", reason: "remote-state-uncertain" }), + reconcile: () => ({ kind: "unknown", reason: "not-used" }), + }; + expect(uncertainReplan.coordinator.dispatchJudge( + uncertainReplan.threshold.permit, + judgePort().port, + effectPossiblePort, + )).toEqual({ kind: "AWAITING_HUMAN", reason: "quality-replan-effect-possible" }); + + const boundedReplan = reachFirstThreshold(); + const noEffectPort: QualityReplanPort = { + dispatch: (request) => ({ kind: "no-effect-confirmed", attestationId: `no-effect-${request.attemptNo}` }), + reconcile: () => ({ kind: "unknown", reason: "not-used" }), + }; + expect(boundedReplan.coordinator.dispatchJudge( + boundedReplan.threshold.permit, + judgePort().port, + noEffectPort, + )).toEqual({ kind: "AWAITING_HUMAN", reason: "quality-replan-attempts-exhausted" }); + }); + + test("rejects late replan outcomes after a concurrent reconciliation wins", () => { + const lateCompletion = reachFirstThreshold(); + let reconciledKind = "not-run"; + const completionAfterReconcile: QualityReplanPort = { + dispatch(request) { + const reconciled = lateCompletion.coordinator.resumeReplan(request.qualityScopeId, { + dispatch: () => ({ kind: "unknown", reason: "not-used" }), + reconcile: (pending) => ({ + kind: "completed", + receipt: { + judgeInvocationId: pending.judgeInvocationId, + planDigest: `sha256:${"1".repeat(64)}`, + agentId: "reconcile-agent", + contextId: "reconcile-context", + }, + }), + }); + reconciledKind = reconciled.kind; + return { + kind: "completed", + receipt: { + judgeInvocationId: request.judgeInvocationId, + planDigest: `sha256:${"2".repeat(64)}`, + agentId: "late-agent", + contextId: "late-context", + }, + }; + }, + reconcile: () => ({ kind: "unknown", reason: "not-used" }), + }; + expect(lateCompletion.coordinator.dispatchJudge( + lateCompletion.threshold.permit, + judgePort().port, + completionAfterReconcile, + )).toEqual({ kind: "CONFLICT", reason: "quality-replan-attempt-not-pending" }); + expect(reconciledKind).toBe("replanned"); + + const lateNoEffect = reachFirstThreshold(); + const noEffectAfterReconcile: QualityReplanPort = { + dispatch(request) { + lateNoEffect.coordinator.resumeReplan(request.qualityScopeId, { + dispatch: () => ({ kind: "unknown", reason: "not-used" }), + reconcile: (pending) => ({ + kind: "completed", + receipt: { + judgeInvocationId: pending.judgeInvocationId, + planDigest: `sha256:${"3".repeat(64)}`, + agentId: "reconcile-agent", + contextId: "reconcile-context", + }, + }), + }); + return { kind: "no-effect-confirmed", attestationId: "late-no-effect" }; + }, + reconcile: () => ({ kind: "unknown", reason: "not-used" }), + }; + expect(lateNoEffect.coordinator.dispatchJudge( + lateNoEffect.threshold.permit, + judgePort().port, + noEffectAfterReconcile, + )).toEqual({ kind: "CONFLICT", reason: "quality-replan-attempt-not-pending" }); + }); + + test("fails closed when the Quality projection and Loop latch disagree", () => { + const outOfOrder = reachFirstThreshold(); + outOfOrder.coordinator.dispatchJudge(outOfOrder.threshold.permit, judgePort().port, replanPort); + let previous = outOfOrder.previous; + let threshold: ReturnType | null = null; + for (let index = 0; index < 2; index += 1) { + threshold = outOfOrder.coordinator.recordEvidence( + batch(outOfOrder.activation.graph.graphRevision, previous), + trace, + ); + previous = threshold.snapshot; + } + if (threshold?.kind !== "judge-reserved") throw new Error("missing stalled threshold"); + const current = outOfOrder.repository.readProjection(threshold.snapshot.qualityScopeId)!; + const alteredProjection = { + ...current, + lastProgress: { kind: "initial" as const, threshold: 2 }, + }; + outOfOrder.repository.transaction(current.qualityScopeId, (append) => append({ + type: "QUALITY_SNAPSHOT_OBSERVED", + snapshotFingerprint: current.latestSnapshot!.snapshotFingerprint, + progress: alteredProjection.lastProgress, + projection: alteredProjection, + })); + expect(outOfOrder.coordinator.dispatchJudge(threshold.permit, judgePort().port, replanPort)).toEqual({ + kind: "CONFLICT", + reason: "quality-stall-route-out-of-order", + }); + + const stalled = reachStalledRuntime(); + const qualityOnlyRepository = createMemoryQualityRepairRepository({ + initialTransactions: stalled.repository.readTransactions().map((transaction) => ({ + ...transaction, + loopEventSets: [], + })), + }); + const qualityOnlyCoordinator = createQualityRepairCoordinator({ + activation: stalled.activation, + repository: qualityOnlyRepository, + }); + const humanAlternative = stalled.status.resumeCondition.alternatives.find((item) => item.kind === "human-retry")!; + expect(qualityOnlyCoordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: humanAlternative.identity, + humanRetry: { verified: true, eventType: "HUMAN_TURN", actor: "human", turnId: "recovery-turn" }, + })).toEqual({ kind: "CONFLICT", reason: "quality-loop-latch-clear-failed" }); + }); + + test("rejects conflicting canonical Quality Repair transactions in the audit ledger", () => { + const projectDir = createTestProject(); + try { + const source = runtime(); + source.coordinator.recordEvidence(batch(source.activation.graph.graphRevision, null), trace); + const canonical = source.repository.readTransactions()[0]!; + const conflicting: QualityRepairTransaction = { ...canonical, qualityEvents: [] }; + const shard = seededAuditShard(projectDir); + mkdirSync(dirname(shard), { recursive: true }); + const row = (transaction: QualityRepairTransaction, seq: number) => JSON.stringify({ + schemaVersion: 1, + seq, + cloneId: "quality-repair-test", + intentId: "quality-repair-test", + timestamp: `2026-08-04T00:00:0${seq}.000Z`, + heading: "Quality Repair Transaction Committed", + event: "QUALITY_REPAIR_TRANSACTION_COMMITTED", + fields: { Transaction: JSON.stringify(transaction) }, + }); + writeFileSync(shard, `${row(canonical, 1)}\n${row(conflicting, 2)}\n`); + + expect(() => readQualityRepairTransactionsFromAudit(projectDir, DEFAULT_RECORD_DIR, "default")).toThrow( + "invalid-quality-repair-audit-row:transaction-conflict", + ); + } finally { + cleanupTestProject(projectDir); + } + }); + + test("persists Quality Repair transactions through the audit repository and replays them across sessions", () => { + const projectDir = setupIntegrationProject({ noAidlcDocs: true, stripEnvScope: true }); + try { + const record = "quality-audit-00000000"; + const intentsRoot = join(projectDir, "amadeus", "spaces", "default", "intents"); + mkdirSync(join(intentsRoot, record, "audit"), { recursive: true }); + writeFileSync(join(projectDir, "amadeus", "active-space"), "default\n", "utf-8"); + writeFileSync(join(intentsRoot, "active-intent"), `${record}\n`, "utf-8"); + + const activation = resolveQualityPluginActivation({ + mode: "semi", + projection: emptyQualityPluginProjection("intent-1"), + contribution: createFirstPartyQualityContribution(2), + }); + if (activation.kind !== "active") throw new Error("expected active contribution"); + const repository = createAuditQualityRepairRepository({ projectDir, intent: record, space: "default" }); + const observed = createQualityRepairCoordinator({ activation, repository }).recordEvidence( + batch(activation.graph.graphRevision, null), + trace, + ); + expect(observed.kind).toBe("repair"); + expect(readQualityRepairTransactionsFromAudit(projectDir, record, "default")).toHaveLength(1); + + const reloaded = createAuditQualityRepairRepository({ projectDir, intent: record, space: "default" }); + expect(reloaded.readTransactions()).toEqual(repository.readTransactions()); + expect(replayQualityRepairScope(reloaded, observed.snapshot.qualityScopeId)).toMatchObject({ + status: { outcome: "active", workflowExecutionState: "running" }, + transactionCount: 1, + }); + + const resumedCoordinator = createQualityRepairCoordinator({ activation, repository: reloaded }); + expect(resumedCoordinator.recordEvidence( + batch(activation.graph.graphRevision, observed.snapshot), + trace, + )).toMatchObject({ kind: "repair" }); + expect(readQualityRepairTransactionsFromAudit(projectDir, record, "default")).toHaveLength(2); + } finally { + cleanupTestProject(projectDir); + } + }); + + test("validates every stalled-resume prerequisite before accepting improved evidence", () => { + const empty = runtime(); + expect(empty.coordinator.status("missing-scope")).toBeNull(); + expect(empty.coordinator.resumeReplan("missing-scope", replanPort)).toEqual({ + kind: "CONFLICT", + reason: "quality-replan-attempt-not-pending", + }); + expect(empty.coordinator.resume({ + qualityScopeId: "missing-scope", + alternativeIdentity: "missing-alternative", + })).toEqual({ kind: "CONFLICT", reason: "quality-stalled-latch-not-found" }); + + const stalled = reachStalledRuntime(); + const evidenceAlternative = stalled.status.resumeCondition.alternatives.find((item) => + item.kind === "evidence-change" + )!; + const humanAlternative = stalled.status.resumeCondition.alternatives.find((item) => item.kind === "human-retry")!; + + expect(stalled.coordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: humanAlternative.identity, + humanRetry: { + verified: false, + eventType: "HUMAN_TURN", + actor: "human", + turnId: "unverified-turn", + } as unknown as VerifiedHumanTurn, + })).toEqual({ kind: "CONFLICT", reason: "quality-human-retry-not-verified" }); + expect(stalled.coordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + })).toEqual({ kind: "CONFLICT", reason: "quality-resume-evidence-missing" }); + expect(stalled.coordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + evidence: { ...batch(stalled.activation.graph.graphRevision, stalled.previous), observations: [] }, + })).toMatchObject({ kind: "INCOMPLETE" }); + expect(stalled.coordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + evidence: batch(stalled.activation.graph.graphRevision, stalled.previous), + })).toEqual({ kind: "CONFLICT", reason: "quality-evidence-did-not-improve" }); + + expect(stalled.coordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + evidence: batch(stalled.activation.graph.graphRevision, stalled.previous, []), + })).toEqual({ kind: "CONFLICT", reason: "quality-resume-trace-missing" }); + + const resumed = stalled.coordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + evidence: batch(stalled.activation.graph.graphRevision, stalled.previous, []), + trace, + }); + expect(resumed.kind).toBe("resumed"); + if (resumed.kind !== "resumed") throw new Error("expected evidence resume"); + const resumedState = stalled.repository.readProjection(stalled.status.qualityScopeId)!; + expect(stalled.coordinator.status(stalled.status.qualityScopeId)).toMatchObject({ + outcome: "active", + workflowExecutionState: "running", + unresolvedObligationIds: [], + evidenceFingerprint: resumedState.latestSnapshot?.snapshotFingerprint, + }); + const resumeTransaction = stalled.repository.readTransactions().at(-1)!; + expect(resumeTransaction.loopEventSets.flatMap((set) => set.events.map((event) => event.type))).toEqual([ + "LOOP_LATCH_CLEARED", + "WORKFLOW_UNPARKED", + "LOOP_DELIVERY_OBSERVED", + ]); + expect(stalled.coordinator.recordEvidence( + batch(stalled.activation.graph.graphRevision, resumedState.latestSnapshot, []), + trace, + )).toMatchObject({ kind: "repair" }); + }); + + test("fails closed when the evidence resume cannot commit its loop delivery", () => { + const stalled = reachStalledRuntime(); + const evidenceAlternative = stalled.status.resumeCondition.alternatives.find((item) => + item.kind === "evidence-change" + )!; + // Reject only the append the resume delivery flows through, leaving the + // quality transaction and every loop read intact. + const rejectingRepository = { + ...stalled.repository, + loopRepository: { + ...stalled.repository.loopRepository, + transaction: ((partition, body) => + stalled.repository.loopRepository.transaction(partition, (sets) => + body(sets, () => { + throw new Error("resume-append-rejected"); + }))) as typeof stalled.repository.loopRepository.transaction, + }, + }; + const coordinator = createQualityRepairCoordinator({ + activation: stalled.activation, + repository: rejectingRepository, + }); + + const before = rejectingRepository.readTransactions().length; + expect(coordinator.resume({ + qualityScopeId: stalled.status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + evidence: batch(stalled.activation.graph.graphRevision, stalled.previous, []), + trace, + })).toEqual({ kind: "CONFLICT", reason: "quality-loop-evidence-resume-failed" }); + expect(rejectingRepository.readTransactions()).toHaveLength(before); + expect(coordinator.status(stalled.status.qualityScopeId)).toMatchObject({ + outcome: "parked", + stopReason: "REPAIR_STALLED", + }); + }); + + test("resumes a stalled sensor obligation from a newly verified success receipt", () => { + const instance = runtime(); + let previous: QualityEvidenceSnapshot | null = null; + let threshold: ReturnType | null = null; + for (let index = 0; index < 3; index += 1) { + threshold = instance.coordinator.recordEvidence( + sensorBatch(instance.activation.graph.graphRevision, previous, "failed"), + trace, + ); + previous = threshold.snapshot; + } + if (threshold?.kind !== "judge-reserved") throw new Error("missing sensor replan threshold"); + instance.coordinator.dispatchJudge(threshold.permit, judgePort().port, replanPort); + for (let index = 0; index < 2; index += 1) { + threshold = instance.coordinator.recordEvidence( + sensorBatch(instance.activation.graph.graphRevision, previous, "failed"), + trace, + ); + previous = threshold.snapshot; + } + if (threshold?.kind !== "judge-reserved") throw new Error("missing sensor stalled threshold"); + expect(instance.coordinator.dispatchJudge(threshold.permit, judgePort().port, replanPort).kind).toBe( + "REPAIR_STALLED", + ); + const status = instance.coordinator.status(threshold.snapshot.qualityScopeId); + if (status === null) throw new Error("missing sensor stalled status"); + const evidenceAlternative = status.resumeCondition.alternatives.find((item) => item.kind === "evidence-change"); + if (evidenceAlternative === undefined) throw new Error("missing sensor evidence alternative"); + + expect(instance.coordinator.resume({ + qualityScopeId: status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + evidence: sensorBatch(instance.activation.graph.graphRevision, previous, "passed"), + trace, + })).toMatchObject({ kind: "resumed" }); + }); + + test("accepts a replacement verifier receipt while the unresolved set shrinks", () => { + const instance = runtime(); + let previous: QualityEvidenceSnapshot | null = null; + let threshold: ReturnType | null = null; + for (let index = 0; index < 3; index += 1) { + threshold = instance.coordinator.recordEvidence( + mixedSensorBatch(instance.activation.graph.graphRevision, previous, true, `sha256:${"4".repeat(64)}`), + trace, + ); + previous = threshold.snapshot; + } + if (threshold?.kind !== "judge-reserved") throw new Error("missing mixed sensor replan threshold"); + instance.coordinator.dispatchJudge(threshold.permit, judgePort().port, replanPort); + for (let index = 0; index < 2; index += 1) { + threshold = instance.coordinator.recordEvidence( + mixedSensorBatch(instance.activation.graph.graphRevision, previous, true, `sha256:${"4".repeat(64)}`), + trace, + ); + previous = threshold.snapshot; + } + if (threshold?.kind !== "judge-reserved") throw new Error("missing mixed sensor stalled threshold"); + expect(instance.coordinator.dispatchJudge(threshold.permit, judgePort().port, replanPort).kind).toBe( + "REPAIR_STALLED", + ); + const status = instance.coordinator.status(threshold.snapshot.qualityScopeId); + if (status === null) throw new Error("missing mixed sensor stalled status"); + const evidenceAlternative = status.resumeCondition.alternatives.find((item) => item.kind === "evidence-change"); + if (evidenceAlternative === undefined) throw new Error("missing mixed sensor evidence alternative"); + + expect(instance.coordinator.resume({ + qualityScopeId: status.qualityScopeId, + alternativeIdentity: evidenceAlternative.identity, + evidence: mixedSensorBatch( + instance.activation.graph.graphRevision, + previous, + false, + `sha256:${"5".repeat(64)}`, + ), + trace, + })).toMatchObject({ kind: "resumed" }); + }); +}); diff --git a/tests/integration/t430-quality-repair-five-harness-projection.integration.test.ts b/tests/integration/t430-quality-repair-five-harness-projection.integration.test.ts new file mode 100644 index 000000000..9759961de --- /dev/null +++ b/tests/integration/t430-quality-repair-five-harness-projection.integration.test.ts @@ -0,0 +1,40 @@ +// covers: file:scripts/package.ts +// size: medium + +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import claudeManifest from "../../packages/framework/harness/claude/manifest.ts"; +import codexManifest from "../../packages/framework/harness/codex/manifest.ts"; +import cursorManifest from "../../packages/framework/harness/cursor/manifest.ts"; +import kimiManifest from "../../packages/framework/harness/kimi/manifest.ts"; +import opencodeManifest from "../../packages/framework/harness/opencode/manifest.ts"; + +const ROOT = join(import.meta.dir, "..", ".."); +const TOOL_FILES = [ + "amadeus-quality-repair.ts", + "amadeus-quality-repair-runtime.ts", + "amadeus-quality-repair-replay.ts", +] as const; + +const currentHarnessCohort = [ + { name: "claude", manifest: claudeManifest }, + { name: "codex", manifest: codexManifest }, + { name: "cursor", manifest: cursorManifest }, + { name: "opencode", manifest: opencodeManifest }, + { name: "kimi", manifest: kimiManifest }, +] as const; + +describe("Quality Repair current five-harness projection", () => { + for (const { name, manifest } of currentHarnessCohort) { + test(`${name} consumes the shared quality runtime without a harness Core fork`, () => { + expect(manifest.coreDirs).toContainEqual({ src: "tools", dst: "tools" }); + for (const file of TOOL_FILES) { + const source = readFileSync(join(ROOT, "packages", "framework", "core", "tools", file), "utf8"); + const projected = readFileSync(join(ROOT, "dist", name, manifest.harnessDir, "tools", file), "utf8"); + expect(projected, `${name}:${file}`).toBe(source); + } + }); + } +}); diff --git a/tests/no-silent-drop/baseline.json b/tests/no-silent-drop/baseline.json index c56ef4b4e..9b9859159 100644 --- a/tests/no-silent-drop/baseline.json +++ b/tests/no-silent-drop/baseline.json @@ -5,7 +5,7 @@ "revision": "b97929d340b7d951d1dd842aea19425af18be531", "censusDigest": "c287c52b60e88159c0f302476f058cbaee6c0f46303fed56deef19f6df305ec8", "approvalDigest": "d888f26d233fb8043ca7ad2fee8a983a18d8770447fd147c3142e5a0d3ca9e2e", - "previousDigest": "91e56bf7b35c07ef53ce5575decaf827b335cb36e99f5785e13b759a836a51df" + "previousDigest": "241c36679f8486fb804259e09ca79bd71a7368f71c3626038d2eba8d7987907d" }, "entries": [ { diff --git a/tests/no-silent-drop/exemptions.json b/tests/no-silent-drop/exemptions.json index c9a87def0..225ead73f 100644 --- a/tests/no-silent-drop/exemptions.json +++ b/tests/no-silent-drop/exemptions.json @@ -1,5 +1,5 @@ { "schemaVersion": 1, - "previousDigest": "42ee9698681d214a15a88dfc88103a0c2234fe878f733e41c716b4d62506771e", + "previousDigest": "c8c0c1f4780a9cf4c0482a78f22832af8c69493ccc3dd7e980d470a2b7dc7e7f", "entries": [] } diff --git a/tests/unit/t28-audit-event-sync.test.ts b/tests/unit/t28-audit-event-sync.test.ts index 09a96b2db..3561b96f8 100644 --- a/tests/unit/t28-audit-event-sync.test.ts +++ b/tests/unit/t28-audit-event-sync.test.ts @@ -46,7 +46,7 @@ // .sh test 4 (every MD event in TS) -> "every audit-format.md event appears in amadeus-audit.ts" // .sh test 5 (EVENT_HEADINGS has every TS event) -> "EVENT_HEADINGS maps every VALID_EVENT_TYPES member" // .sh test 6 (assert_eq TS_COUNT MD_COUNT) -> "event counts match across the two files" -// .sh test 7 (assert_eq TS_COUNT - baseline pin) -> "VALID_EVENT_TYPES.size === 86 (baseline pin)" +// .sh test 7 (assert_eq TS_COUNT - baseline pin) -> "VALID_EVENT_TYPES.size === 87 (baseline pin)" import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; @@ -74,8 +74,9 @@ const AUDIT_MD = join(AMADEUS_SRC, "knowledge", "amadeus-shared", "audit-format. // +UNIT_POOL_EVENT_SET_COMMITTED (#1919 fixed-width Unit pool) takes it to 81; // +GOAL_CHANGE_PROPOSED +GOAL_REVISION_APPROVED +GOAL_RECONCILED // +LEGACY_GOAL_MIGRATED take it to 85; -// +LOOP_MONITOR_EVENT_SET_COMMITTED (#2095) takes it to 86. -const CANONICAL_COUNT = 86; +// +LOOP_MONITOR_EVENT_SET_COMMITTED (#2095) takes it to 86; +// +QUALITY_REPAIR_TRANSACTION_COMMITTED (#2096) takes it to 87. +const CANONICAL_COUNT = 87; /** Slice the lines of `text` BETWEEN the first line matching `start` and the * next line matching `end` (inclusive of both), reproducing `sed -n @@ -178,7 +179,7 @@ describe("t28 audit event-type sync (migrated from t28-audit-event-sync.sh, plan // .sh test 7: assert_eq TS_COUNT - the canonical baseline pin, bumped when // events are added or removed. (#367 added WORKFLOW_PARKED/UNPARKED -> 69; // #369 removed TEST_RUN_MODE_ENABLED -> 68; HUMAN_TURN took it to 69; the adaptive composer added RECOMPOSED -> 70; #671 added DELEGATED_APPROVAL -> 71; #685 added DELEGATED_REJECTION -> 72; #499/#848 added GUARD_EXEMPTED -> 73; #1125 added GRANT_ISSUED + GRANT_REVOKED -> 75; lifecycle transactions add two -> 77.) - test("VALID_EVENT_TYPES.size === 86 (baseline pin) [.sh test 7]", () => { + test("VALID_EVENT_TYPES.size === 87 (baseline pin) [.sh test 7]", () => { expect(TS_EVENTS.length).toBe(CANONICAL_COUNT); }); }); diff --git a/tests/unit/t426-loop-monitor.test.ts b/tests/unit/t426-loop-monitor.test.ts index 86f60a212..7f52d2224 100644 --- a/tests/unit/t426-loop-monitor.test.ts +++ b/tests/unit/t426-loop-monitor.test.ts @@ -6,6 +6,7 @@ import { describe, expect, test } from "bun:test"; import { applyLoopDelivery, compileLoopMonitorManifest, + createJudgeRouteConstraint, createLoopDelivery, createLoopMonitorProjection, type CompiledLoopMonitor, @@ -78,6 +79,7 @@ function delivery( predecessorDeliveryId: string | null, upstreamEventIdentity: string, payloadFingerprint = `sha256:${"b".repeat(64)}`, + routeConstraint = monitor.routeConstraint, ) { return createLoopDelivery({ partition, @@ -90,7 +92,7 @@ function delivery( references: [{ kind: "artifact", id: "code-summary", digest: `sha256:${"c".repeat(64)}` }], }, evidence, - routeConstraint: monitor.routeConstraint, + routeConstraint, trace, }); } @@ -305,6 +307,42 @@ describe("loop monitor manifest compiler", () => { }); describe("loop monitor delivery reducer", () => { + test("admits a content-bound non-empty route subset without quality-specific branching", () => { + const monitor = compiled(); + const replanOnly = createJudgeRouteConstraint(monitor, ["replan"]); + expect(replanOnly.routeIds).toEqual(["replan"]); + expect(replanOnly.fingerprint).not.toBe(monitor.routeConstraint.fingerprint); + + const constrained = createLoopDelivery({ + ...delivery(monitor, "quality-check", null, "subset-route"), + routeConstraint: replanOnly, + }); + expect(applyLoopDelivery(createLoopMonitorProjection(partition, monitor), monitor, constrained).ok).toBe(true); + expect(() => createJudgeRouteConstraint(monitor, [])).toThrow("loop-monitor-route-constraint-empty"); + expect(() => createJudgeRouteConstraint(monitor, ["unknown"])).toThrow( + "loop-monitor-route-constraint-unknown-route", + ); + expect(() => createJudgeRouteConstraint(monitor, ["replan", "replan"])).toThrow( + "loop-monitor-route-constraint-duplicate-route", + ); + }); + + test("rejects a delivery whose route constraint no longer resolves against the monitor", () => { + const monitor = compiled(); + const tampered = { + ...delivery(monitor, "quality-check", null, "tampered-route"), + routeConstraint: { + routeIds: ["replan", "replan"], + fingerprint: monitor.routeConstraint.fingerprint, + }, + }; + expect(applyLoopDelivery(createLoopMonitorProjection(partition, monitor), monitor, tampered)).toEqual({ + ok: false, + status: "CONFLICT", + reason: "route-constraint-mismatch", + }); + }); + test("delivery construction admits identifiers and digests only, never raw evidence", () => { const monitor = compiled(); expect(() => createLoopDelivery({ @@ -398,6 +436,38 @@ describe("loop monitor delivery reducer", () => { }); }); + test("binds route constraints into delivery identity and rejects full/subset reuse in either arrival order", () => { + const monitor = compiled(); + const subsetConstraint = createJudgeRouteConstraint(monitor, [monitor.routeConstraint.routeIds[0]!]); + const full = delivery(monitor, "quality-check", null, "same-route-identity"); + const subset = delivery( + monitor, + "quality-check", + null, + "same-route-identity", + full.payloadFingerprint, + subsetConstraint, + ); + + expect(full.deliveryId).not.toBe(subset.deliveryId); + const fullFirst = accepted( + applyLoopDelivery(createLoopMonitorProjection(partition, monitor), monitor, full), + ); + expect(applyLoopDelivery(fullFirst, monitor, subset)).toMatchObject({ + ok: false, + status: "CONFLICT", + reason: "delivery-identity-payload-conflict", + }); + const subsetFirst = accepted( + applyLoopDelivery(createLoopMonitorProjection(partition, monitor), monitor, subset), + ); + expect(applyLoopDelivery(subsetFirst, monitor, full)).toMatchObject({ + ok: false, + status: "CONFLICT", + reason: "delivery-identity-payload-conflict", + }); + }); + test("buffers a successor until its predecessor arrives, then drains causally", () => { const monitor = compiled(); const parent = delivery(monitor, "quality-check", null, "parent"); diff --git a/tests/unit/t427-loop-monitor-runtime.test.ts b/tests/unit/t427-loop-monitor-runtime.test.ts index c6204b485..3d7a4d28a 100644 --- a/tests/unit/t427-loop-monitor-runtime.test.ts +++ b/tests/unit/t427-loop-monitor-runtime.test.ts @@ -166,6 +166,13 @@ describe("Loop Monitor Judge runtime", () => { deliveryId: `delivery-${"f".repeat(32)}`, payloadFingerprint: `sha256:${"f".repeat(64)}`, })).toMatchObject({ kind: "CONFLICT", reason: "upstream-identity-payload-conflict" }); + expect(coordinator.observeDelivery({ + ...first, + routeConstraint: { + routeIds: [first.routeConstraint.routeIds[0]!], + fingerprint: `sha256:${"f".repeat(64)}`, + }, + })).toMatchObject({ kind: "CONFLICT", reason: "upstream-identity-payload-conflict" }); expect(coordinator.observeDelivery( delivery(compiled, partition, "repair", first.deliveryId, "forked-successor"), )).toMatchObject({ kind: "CONFLICT", reason: "causal-fork" }); @@ -302,11 +309,13 @@ describe("Loop Monitor Judge runtime", () => { dispatch: (request) => ({ kind: "completed", result: resultFor(request.invocationId, routeGraph) }), reconcile: () => ({ kind: "unknown", reason: "not-used" }), }).kind).toBe("route-applied"); - expect(coordinator.readProjection(partition).judgeRedispatchAttempts).toBe(0); + expect(coordinator.readProjection(partition)).toMatchObject({ + judgeRedispatchAttempts: 0, + lastSemanticEventId: "repair", + matchedPrefix: 2, + }); - const repair = delivery(routeGraph, partition, "repair", q2.deliveryId, "second-repair"); - expect(coordinator.observeDelivery(repair).kind).toBe("observed"); - const quality = delivery(routeGraph, partition, "quality-check", repair.deliveryId, "second-quality"); + const quality = delivery(routeGraph, partition, "quality-check", q2.deliveryId, "second-quality"); const second = coordinator.observeDelivery(quality); expect(second.kind).toBe("judge-reserved"); if (second.kind !== "judge-reserved") throw new Error(`expected second Judge reservation, got ${second.kind}`); diff --git a/tests/unit/t428-quality-repair.test.ts b/tests/unit/t428-quality-repair.test.ts new file mode 100644 index 000000000..f418bb764 --- /dev/null +++ b/tests/unit/t428-quality-repair.test.ts @@ -0,0 +1,464 @@ +// covers: file:packages/framework/core/tools/amadeus-quality-repair.ts +// size: medium + +import { describe, expect, test } from "bun:test"; + +import { + collectQualityObservations, + createFirstPartyQualityContribution, + createQualityEpochProjection, + emptyQualityPluginProjection, + normalizeQualityEvidence, + planNoneModeQualitySetting, + planQualityDelivery, + qualityDigest, + recordQualityReplan, + resolveQualityPluginActivation, + type QualityEvidenceBatchInput, + type QualityEvidenceSnapshot, + type QualityObservation, +} from "../../packages/framework/core/tools/amadeus-quality-repair.ts"; + +const scope = { + intentUuid: "intent-1", + monitorId: "quality-repair", + stageInstanceId: "code-generation-1", + boltId: "bolt-2", + graphRevision: "sha256:graph-1", +}; + +function reviewer(blockerIds: readonly string[]): Extract { + return { + kind: "reviewer", + invocationId: "review-1", + verifierId: "architecture-reviewer", + validationReceipt: `sha256:${"a".repeat(64)}`, + verdict: blockerIds.length === 0 ? "READY" : "NOT READY", + blockers: blockerIds.map((id) => ({ + findingId: id, + artifactId: "code-summary", + failureFingerprint: `sha256:${id.charCodeAt(0).toString(16).padStart(2, "0").repeat(32)}`, + })), + }; +} + +function batch( + observations: readonly QualityObservation[], + previousSnapshot: QualityEvidenceSnapshot | null = null, +): QualityEvidenceBatchInput { + return { + ...scope, + providerId: "quality-evidence-v1", + previousSnapshot, + observations, + }; +} + +function snapshot( + blockerIds: readonly string[], + previousSnapshot: QualityEvidenceSnapshot | null = null, +): QualityEvidenceSnapshot { + const result = normalizeQualityEvidence(batch([reviewer(blockerIds)], previousSnapshot)); + if (!result.ok) throw new Error(result.error.message); + return result.snapshot; +} + +describe("first-party Quality Repair contribution", () => { + test("fails closed by mode and only accepts a real none-mode human opt-in", () => { + const contribution = createFirstPartyQualityContribution(2); + const initial = emptyQualityPluginProjection("intent-1"); + expect(resolveQualityPluginActivation({ mode: "none", projection: initial, contribution })).toEqual({ + kind: "disabled", + reason: "none-default-off", + }); + expect(resolveQualityPluginActivation({ mode: "semi", projection: initial, contribution: null })).toMatchObject({ + kind: "error", + error: { code: "ACTIVATION_FAILED" }, + }); + expect(planNoneModeQualitySetting(initial, true, null)).toMatchObject({ + ok: false, + error: { code: "PROVENANCE_REQUIRED" }, + }); + + const optedIn = planNoneModeQualitySetting(initial, true, { + verified: true, + eventType: "HUMAN_TURN", + actor: "human", + turnId: "turn-1", + }); + expect(optedIn.ok).toBe(true); + if (!optedIn.ok) return; + const active = resolveQualityPluginActivation({ mode: "none", projection: optedIn.projection, contribution }); + expect(active.kind).toBe("active"); + if (active.kind !== "active") return; + expect(active.graph.loopMonitors[0]?.threshold).toBe(2); + expect(active.contribution.requiredOutputs).toEqual([]); + expect(active.graph.loopMonitors[0]?.routes.map((route) => route.id)).toEqual([ + "repair", + "replan", + "repair-stalled", + ]); + expect(active.graph.loopMonitors[0]?.routes.find((route) => route.id === "replan")).toMatchObject({ + kind: "transition", + targetEvent: "QUALITY_NON_PROGRESS", + }); + expect(active.graph.loopMonitors[0]?.transitionTable).not.toHaveProperty("QUALITY_REPLAN"); + }); + + test("fails closed for tampered contributions and records none-mode opt-out", () => { + const contribution = createFirstPartyQualityContribution(2); + expect(resolveQualityPluginActivation({ + mode: "full", + projection: emptyQualityPluginProjection("intent-1"), + contribution: { ...contribution, contentDigest: qualityDigest("tampered") }, + })).toMatchObject({ kind: "error", error: { code: "ACTIVATION_FAILED" } }); + + const invalidRules = contribution.routeRules.map((rule) => + rule.routeId === "repair-stalled" ? { ...rule, disposition: "continue" as const } : rule + ); + const { contentDigest: _contentDigest, ...content } = contribution; + const invalidContribution = { ...content, routeRules: invalidRules }; + expect(resolveQualityPluginActivation({ + mode: "full", + projection: emptyQualityPluginProjection("intent-1"), + contribution: { ...invalidContribution, contentDigest: qualityDigest(invalidContribution) }, + })).toMatchObject({ kind: "error", error: { code: "ACTIVATION_FAILED" } }); + + const optedOut = planNoneModeQualitySetting(emptyQualityPluginProjection("intent-1"), false, { + verified: true, + eventType: "HUMAN_TURN", + actor: "human", + turnId: "turn-opt-out", + }); + expect(optedOut).toMatchObject({ + ok: true, + projection: { noneModeOptedIn: false, provenanceTurnId: null }, + event: { type: "QUALITY_REPAIR_OPTED_OUT" }, + }); + }); + + test("rejects invalid plugin identities and contribution thresholds", () => { + expect(planNoneModeQualitySetting( + { ...emptyQualityPluginProjection("intent-1"), intentUuid: "invalid intent" }, + true, + { verified: true, eventType: "HUMAN_TURN", actor: "human", turnId: "turn-1" }, + )).toMatchObject({ ok: false, error: { code: "INTENT_MISMATCH" } }); + expect(() => createFirstPartyQualityContribution(0)).toThrow("quality-repair-threshold-must-be-positive"); + expect(qualityDigest({ intentUuid: "intent-1" })).toStartWith("sha256:"); + }); +}); + +describe("blocking evidence normalization", () => { + test("normalizes only declared blocking sources and computes canonical deltas", () => { + const observations = collectQualityObservations([ + reviewer(["blocker-a"]), + { + kind: "sensor", + sensorId: "advisory-lint", + blocking: false, + status: "failed", + outputId: "lint-output", + terminalReceipt: `sha256:${"b".repeat(64)}`, + }, + { + kind: "sensor", + sensorId: "type-check", + blocking: true, + status: "incomplete", + outputId: "typecheck-output", + terminalReceipt: null, + }, + { + kind: "produce", + outputId: "code-summary", + required: true, + status: "missing", + verifierId: "artifact-verifier", + receipt: null, + }, + { + kind: "condition", + conditionKind: "completion", + conditionId: "tests-green", + status: "unsatisfied", + verifierId: "test-runner", + receipt: `sha256:${"c".repeat(64)}`, + }, + { kind: "human-request-changes", turnId: "turn-2" }, + ]); + expect(observations).toHaveLength(5); + const first = normalizeQualityEvidence(batch(observations)); + expect(first.ok).toBe(true); + if (!first.ok) return; + expect(first.snapshot.unresolved.map( + (item) => `${item.sourceCategory}:${item.failureKind}`, + ).sort()).toEqual([ + "completion:unmet", + "produce:missing", + "reviewer:blocker", + "sensor:evidence-incomplete", + ]); + expect(first.snapshot.addedIds).toEqual(first.snapshot.unresolved.map((item) => item.obligationId)); + + const second = normalizeQualityEvidence(batch([reviewer([])], first.snapshot)); + expect(second.ok).toBe(true); + if (!second.ok) return; + expect(second.snapshot.unresolved).toEqual([]); + expect(second.snapshot.resolvedIds).toEqual(first.snapshot.unresolved.map((item) => item.obligationId)); + }); + + test("rejects cross-scope previous evidence instead of inventing obligations", () => { + const previous = snapshot(["blocker-a"]); + const result = normalizeQualityEvidence({ + ...batch([reviewer(["blocker-a"])], previous), + graphRevision: "sha256:other", + }); + expect(result).toMatchObject({ ok: false, error: { code: "SCOPE_MISMATCH" } }); + }); + + test("normalizes incomplete, successful, and conflicting evidence fail closed", () => { + const invalidIdentityCases: readonly QualityObservation[] = [ + { ...reviewer(["blocker-a"]), invocationId: "invalid invocation" }, + { + kind: "sensor", + sensorId: "invalid sensor", + blocking: true, + status: "failed", + outputId: "lint-output", + terminalReceipt: `sha256:${"1".repeat(64)}`, + }, + { + kind: "produce", + outputId: "invalid output", + required: true, + status: "missing", + verifierId: "artifact-verifier", + receipt: null, + }, + { + kind: "condition", + conditionKind: "completion", + conditionId: "invalid condition", + status: "unsatisfied", + verifierId: "test-runner", + receipt: null, + }, + ]; + for (const observation of invalidIdentityCases) { + expect(normalizeQualityEvidence(batch([observation]))).toMatchObject({ + ok: false, + error: { code: "INCOMPLETE" }, + }); + } + + const incompleteReviewer = normalizeQualityEvidence(batch([{ + ...reviewer([]), + verdict: "NOT READY", + validationReceipt: "invalid", + }])); + expect(incompleteReviewer).toMatchObject({ + ok: true, + snapshot: { unresolved: [{ sourceCategory: "reviewer", failureKind: "evidence-incomplete" }] }, + }); + expect(normalizeQualityEvidence(batch([{ + ...reviewer([]), + verdict: "NOT-READY", + validationReceipt: "invalid", + }]))).toMatchObject({ + ok: true, + snapshot: { unresolved: [{ sourceCategory: "reviewer", failureKind: "evidence-incomplete" }] }, + }); + expect(normalizeQualityEvidence(batch([{ + ...reviewer(["blocker-a"]), + verdict: "READY", + }]))).toMatchObject({ + ok: false, + error: { code: "INCOMPLETE", message: "reviewer-verdict-conflicts-with-blockers" }, + }); + + const fallbackFingerprint = normalizeQualityEvidence(batch([{ + ...reviewer(["blocker-a"]), + blockers: [{ findingId: "blocker-a", artifactId: "code-summary", failureFingerprint: "invalid" }], + }])); + expect(fallbackFingerprint).toMatchObject({ + ok: true, + snapshot: { unresolved: [{ sourceCategory: "reviewer", failureKind: "blocker" }] }, + }); + + const receipt = `sha256:${"2".repeat(64)}`; + const successes = normalizeQualityEvidence(batch([ + { + kind: "sensor", + sensorId: "type-check", + blocking: true, + status: "passed", + outputId: "typecheck-output", + terminalReceipt: receipt, + }, + { + kind: "produce", + outputId: "code-summary", + required: true, + status: "present", + verifierId: "artifact-verifier", + receipt, + }, + { + kind: "condition", + conditionKind: "completion", + conditionId: "tests-green", + status: "satisfied", + verifierId: "test-runner", + receipt, + }, + ])); + expect(successes).toMatchObject({ ok: true, snapshot: { unresolved: [] } }); + if (!successes.ok) throw new Error(successes.error.message); + expect(successes.snapshot.verifierSuccessReceipts).toHaveLength(3); + + const previous = snapshot(["blocker-a"]); + expect(normalizeQualityEvidence({ + ...batch([reviewer(["blocker-a"])], previous), + epochStartEventIdentity: "different-epoch-start", + })).toMatchObject({ ok: false, error: { code: "SCOPE_MISMATCH" } }); + + const conflicting = reviewer(["blocker-a"]); + expect(normalizeQualityEvidence(batch([ + conflicting, + { + ...conflicting, + blockers: [{ + findingId: "blocker-a", + artifactId: "different-artifact", + failureFingerprint: `sha256:${"3".repeat(64)}`, + }], + }, + ]))).toMatchObject({ + ok: false, + error: { code: "INCOMPLETE", message: "conflicting duplicate quality obligation" }, + }); + + expect(normalizeQualityEvidence(batch([]))).toMatchObject({ + ok: false, + error: { code: "INCOMPLETE" }, + }); + }); +}); + +describe("bounded convergence", () => { + test("uses T+1 snapshots, replans first, and stalls only after the post-replan threshold", () => { + const first = snapshot(["blocker-a"]); + const second = snapshot(["blocker-a"], first); + const third = snapshot(["blocker-a"], second); + let epoch = createQualityEpochProjection(first, 2); + + const initial = planQualityDelivery(epoch, first); + expect(initial.progress.kind).toBe("initial"); + epoch = initial.nextProjection; + const collecting = planQualityDelivery(epoch, second); + expect(collecting.progress).toMatchObject({ kind: "collecting", consecutiveNonProgress: 1 }); + epoch = collecting.nextProjection; + const threshold = planQualityDelivery(epoch, third); + expect(threshold.progress).toMatchObject({ + kind: "threshold", + pattern: "fixed-point", + requiredRoute: "replan", + }); + expect(threshold.routeIds).toEqual(["replan"]); + + epoch = recordQualityReplan(threshold.nextProjection, { + judgeInvocationId: "judge-1", + planDigest: `sha256:${"d".repeat(64)}`, + agentId: "repair-agent", + contextId: "fresh-context-1", + }).nextProjection; + expect(epoch).toMatchObject({ consecutiveNonProgress: 0, replanSinceLastProgress: true }); + + const fourth = snapshot(["blocker-a"], third); + const fifth = snapshot(["blocker-a"], fourth); + epoch = planQualityDelivery(epoch, fourth).nextProjection; + const stalled = planQualityDelivery(epoch, fifth); + expect(stalled.progress).toMatchObject({ + kind: "threshold", + pattern: "fixed-point", + requiredRoute: "repair-stalled", + }); + expect(stalled.routeIds).toEqual(["repair-stalled"]); + expect(stalled.nextProjection.window).toHaveLength(3); + }); + + test("strict subset progress resets the counter and replan flag", () => { + const first = snapshot(["blocker-a", "blocker-b"]); + const same = snapshot(["blocker-a", "blocker-b"], first); + const reduced = snapshot(["blocker-a"], same); + let epoch = createQualityEpochProjection(first, 2); + epoch = planQualityDelivery(epoch, first).nextProjection; + epoch = planQualityDelivery(epoch, same).nextProjection; + epoch = recordQualityReplan(epoch, { + judgeInvocationId: "judge-progress-reset", + planDigest: `sha256:${"e".repeat(64)}`, + agentId: "repair-agent", + contextId: "fresh-context-progress-reset", + }).nextProjection; + const progress = planQualityDelivery(epoch, reduced); + expect(progress.progress.kind).toBe("strict-progress"); + expect(progress.nextProjection).toMatchObject({ + consecutiveNonProgress: 0, + replanSinceLastProgress: false, + }); + }); + + test("keeps a converged empty snapshot out of the non-progress counter", () => { + const first = snapshot([]); + const second = snapshot([], first); + let epoch = createQualityEpochProjection(first, 2); + const initial = planQualityDelivery(epoch, first); + expect(initial).toMatchObject({ + progress: { kind: "strict-progress" }, + nextProjection: { consecutiveNonProgress: 0, replanSinceLastProgress: false }, + routeIds: [], + deterministicAction: null, + }); + epoch = initial.nextProjection; + const repeated = planQualityDelivery(epoch, second); + expect(repeated).toMatchObject({ + progress: { kind: "strict-progress" }, + nextProjection: { consecutiveNonProgress: 0, replanSinceLastProgress: false }, + routeIds: [], + deterministicAction: null, + }); + }); + + test("classifies regression and churn and rejects invalid delivery inputs", () => { + const a1 = snapshot(["blocker-a"]); + const b1 = snapshot(["blocker-b"], a1); + const a2 = snapshot(["blocker-a"], b1); + let regressionEpoch = createQualityEpochProjection(a1, 2); + regressionEpoch = planQualityDelivery(regressionEpoch, a1).nextProjection; + regressionEpoch = planQualityDelivery(regressionEpoch, b1).nextProjection; + expect(planQualityDelivery(regressionEpoch, a2).progress).toMatchObject({ + kind: "threshold", + pattern: "regression", + }); + + const c1 = snapshot(["blocker-c"], b1); + let churnEpoch = createQualityEpochProjection(a1, 2); + churnEpoch = planQualityDelivery(churnEpoch, a1).nextProjection; + churnEpoch = planQualityDelivery(churnEpoch, b1).nextProjection; + expect(planQualityDelivery(churnEpoch, c1).progress).toMatchObject({ + kind: "threshold", + pattern: "churn", + }); + + expect(() => planQualityDelivery( + createQualityEpochProjection(a1, 2), + { ...a1, qualityScopeId: "other-scope" }, + )).toThrow("quality-delivery-scope-mismatch"); + expect(() => recordQualityReplan(createQualityEpochProjection(a1, 2), { + judgeInvocationId: "invalid invocation", + planDigest: "invalid", + agentId: "repair-agent", + contextId: "context-1", + })).toThrow("quality-replan-receipt-invalid"); + }); +}); diff --git a/tests/unit/t429-quality-repair-replay-validation.test.ts b/tests/unit/t429-quality-repair-replay-validation.test.ts new file mode 100644 index 000000000..6c9c205db --- /dev/null +++ b/tests/unit/t429-quality-repair-replay-validation.test.ts @@ -0,0 +1,98 @@ +// covers: file:packages/framework/core/tools/amadeus-quality-repair-replay.ts +// size: small + +import { describe, expect, test } from "bun:test"; + +import { loopMonitorPartitionKey } from "../../packages/framework/core/tools/amadeus-loop-monitor-runtime.ts"; +import { decodeQualityRepairTransaction } from "../../packages/framework/core/tools/amadeus-quality-repair-replay.ts"; + +const partition = { + intentUuid: "intent-1", + monitorId: "quality-repair", + stageInstanceId: "stage-1", + graphRevision: `sha256:${"1".repeat(64)}`, +}; + +function projection() { + return { + qualityScopeId: "quality-scope-1", + partition, + epoch: { + qualityScopeId: "quality-scope-1", + qualityEpochId: "quality-epoch-1", + epochStartEventIdentity: "quality-epoch-start-1", + threshold: 2, + window: [], + consecutiveNonProgress: 0, + replanSinceLastProgress: false, + }, + latestSnapshot: null, + lastProgress: null, + observationSequence: 1, + stalledLatch: null, + workflowExecutionState: "running", + lastReplanReceipt: null, + pendingReplan: null, + }; +} + +function transaction(input: { readonly projection?: unknown; readonly loopEventSets?: readonly unknown[] } = {}) { + return { + schemaVersion: 1, + transactionId: "quality-transaction-1", + qualityScopeId: "quality-scope-1", + qualityEvents: [{ + type: "QUALITY_SNAPSHOT_OBSERVED", + snapshotFingerprint: `sha256:${"2".repeat(64)}`, + progress: {}, + projection: input.projection ?? projection(), + }], + loopEventSets: input.loopEventSets ?? [], + }; +} + +function loopEventSet() { + return { + eventSetId: "loop-event-set-1", + partition, + partitionKey: loopMonitorPartitionKey(partition), + idempotencyKey: "quality-loop-1", + payloadFingerprint: `sha256:${"3".repeat(64)}`, + events: [], + }; +} + +describe("Quality Repair audit replay validation", () => { + test("rejects malformed nested projection collections and pending attempts", () => { + const valid = projection(); + expect(() => decodeQualityRepairTransaction(JSON.stringify(transaction()))).not.toThrow(); + + const malformed = [ + { ...valid, latestSnapshot: {} }, + { ...valid, latestSnapshot: { unresolved: [], verifierSuccessReceipts: "not-an-array" } }, + { ...valid, stalledLatch: {} }, + { ...valid, pendingReplan: {} }, + { ...valid, pendingReplan: { attemptIdentity: 1 } }, + ]; + for (const candidate of malformed) { + expect(() => decodeQualityRepairTransaction(JSON.stringify(transaction({ projection: candidate })))).toThrow( + "invalid-quality-repair-transaction", + ); + } + }); + + test("rejects Loop event sets without a canonical partition key", () => { + const valid = loopEventSet(); + expect(() => decodeQualityRepairTransaction(JSON.stringify(transaction({ loopEventSets: [valid] })))).not.toThrow(); + + for (const candidate of [ + { ...valid, partitionKey: undefined }, + { ...valid, partitionKey: 1 }, + { ...valid, partitionKey: "wrong-partition" }, + ]) { + expect(() => decodeQualityRepairTransaction(JSON.stringify(transaction({ loopEventSets: [candidate] })))).toThrow( + "invalid-quality-repair-transaction", + ); + } + }); +}); diff --git a/tests/unit/t81.test.ts b/tests/unit/t81.test.ts index 6564cd146..9ab7803ad 100644 --- a/tests/unit/t81.test.ts +++ b/tests/unit/t81.test.ts @@ -223,7 +223,8 @@ describe("t81 amadeus-state practices-event — bolt-plan-marker-conflict overri // plus EXECUTION_EVENT_SET_COMMITTED (#1602 audit-first lifecycle, +1) = 80, // plus UNIT_POOL_EVENT_SET_COMMITTED (#1919 fixed-width Unit pool, +1) = 81, // plus LOOP_MONITOR_EVENT_SET_COMMITTED (durable Loop Monitor stream, +1) = 82, - // plus the four Goal Lifecycle events (+4) = 86. + // plus the four Goal Lifecycle events (+4) = 86, + // plus QUALITY_REPAIR_TRANSACTION_COMMITTED (#2096, +1) = 87. const auditSrc = readFileSync( join(REPO_ROOT, "dist", "claude", ".claude", "tools", "amadeus-audit.ts"), "utf-8", @@ -231,7 +232,7 @@ describe("t81 amadeus-state practices-event — bolt-plan-marker-conflict overri const block = auditSrc.match(/const VALID_EVENT_TYPES = new Set\(\[([\s\S]*?)\]\)/); expect(block).not.toBeNull(); const count = (block ? block[1].match(/"[A-Z0-9_]+"/g) : null)?.length ?? -1; - expect(count).toBe(86); + expect(count).toBe(87); }); // --- Test 4: milestone 8 write-failure path coexists (different Reason value) ---