@zq-silk/yui 0.8.3 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/ARCHITECTURE.md +40 -22
  2. package/README.md +66 -19
  3. package/dist/cli/commandCatalog.js +43 -14
  4. package/dist/cli/operatorWizard.js +10 -20
  5. package/dist/cli/updatePorts.js +6 -0
  6. package/dist/cli.js +252 -37
  7. package/dist/commands/executionAuditCommands.js +30 -0
  8. package/dist/commands/globalRoleCommands.js +8 -4
  9. package/dist/commands/operatorCommands.js +42 -1
  10. package/dist/commands/taskCommands.js +527 -147
  11. package/dist/commands/taskCompletionGate.js +36 -24
  12. package/dist/commands/taskContextCommand.js +11 -4
  13. package/dist/commands/taskInputCommands.js +48 -10
  14. package/dist/commands/taskNextActionCommand.js +38 -3
  15. package/dist/commands/taskOverviewCommand.js +2 -1
  16. package/dist/commands/taskRoleRuntimeStatus.js +2 -1
  17. package/dist/context/runContextPack.js +9 -5
  18. package/dist/context/sessionBootstrapManifest.js +158 -11
  19. package/dist/context/wakeNotification.js +5 -3
  20. package/dist/controller/clientRuntime.js +15 -15
  21. package/dist/controller/controller.js +16 -8
  22. package/dist/controller/fileSchedulerStoreAdapter.js +67 -7
  23. package/dist/controller/handoverCandidate.js +10 -3
  24. package/dist/controller/sessionNotify.js +4 -22
  25. package/dist/executor/agentAdapter.js +2 -2
  26. package/dist/executor/agentExecutor.js +29 -9
  27. package/dist/executor/fileRoleLaunchPlanner.js +37 -45
  28. package/dist/integration/gitIntegrationService.js +50 -2
  29. package/dist/integration/integrationCheckEvidenceReuse.js +53 -0
  30. package/dist/observability/executionAudit.js +47 -1
  31. package/dist/observability/faultClassification.js +6 -4
  32. package/dist/observability/orchestrationMetrics.js +196 -0
  33. package/dist/operator/operatorSessionHistory.js +36 -0
  34. package/dist/release/releaseHandover.js +7 -5
  35. package/dist/release/runtimeRelease.js +15 -0
  36. package/dist/repository/taskWorkspaceCoordinator.js +13 -10
  37. package/dist/review/deltaRecheck.js +3 -2
  38. package/dist/review/reviewFindingLedger.js +5 -4
  39. package/dist/review/reviewOutcomeClassifier.js +263 -54
  40. package/dist/review/taskFinalReviewContractEvent.js +1 -0
  41. package/dist/review/taskFinalReviewContractRebind.js +367 -0
  42. package/dist/run/runIdentity.js +10 -70
  43. package/dist/runtime/agentHost.js +3 -4
  44. package/dist/runtime/codexAppServerRuntime.js +6 -0
  45. package/dist/runtime/exactControlPlane.js +47 -37
  46. package/dist/runtime/firstProgressStopLoss.js +54 -0
  47. package/dist/runtime/launchBroker.js +10 -2
  48. package/dist/runtime/runtimeDeadlines.js +14 -0
  49. package/dist/runtime/sessionTitle.js +24 -12
  50. package/dist/runtime/structuredProviderHost.js +7 -1
  51. package/dist/runtime/tmuxAdapters.js +10 -3
  52. package/dist/scheduler/actionability.js +4 -2
  53. package/dist/scheduler/activeRoleRunDelivery.js +20 -18
  54. package/dist/scheduler/activeTaskProgress.js +2 -1
  55. package/dist/scheduler/leaderWakeupProcessor.js +33 -2
  56. package/dist/scheduler/taskExecutionProjection.js +13 -4
  57. package/dist/scheduler/wakeReason.js +1 -0
  58. package/dist/storage/sqliteStore.js +18 -3
  59. package/dist/storage/taskStore.js +14 -3
  60. package/dist/task/completionReadiness.js +48 -19
  61. package/dist/task/deliveryGuard.js +3 -1
  62. package/dist/task/nextAction.js +153 -55
  63. package/dist/task/repairWave.js +14 -1
  64. package/dist/task/task.js +10 -0
  65. package/dist/task/taskRecordRetirement.js +72 -0
  66. package/dist/web/webSnapshot.js +7 -1
  67. package/dist/workItem/workItem.js +6 -4
  68. package/i18n/README.zh-CN.md +48 -9
  69. package/package.json +1 -1
  70. package/skills/yui-leader/SKILL.md +73 -31
  71. package/skills/yui-operator/SKILL.md +58 -10
  72. package/skills/yui-reviewer/SKILL.md +23 -0
  73. package/skills/yui-runtime/SKILL.md +6 -6
@@ -1,4 +1,40 @@
1
1
  import { normalizeRoleAgentSessionText, roleAgentSessionRef, validateRoleSessionSet } from "../executor/agentExecutor.js";
2
+ /** Separates the one selected writer authority from retained conversations. */
3
+ export function projectOperatorStatus(sessions, activeAgentId, activeAdapterId) {
4
+ if (sessions === null) {
5
+ return Object.freeze({
6
+ writer: { state: "unrecorded", agentId: activeAgentId, adapterId: activeAdapterId },
7
+ historicalConversations: []
8
+ });
9
+ }
10
+ validateRoleSessionSet(sessions);
11
+ // The GlobalRole binding is the writer authority. The SessionSet pointer is
12
+ // retained operational state and may lag a Role update, so it cannot select
13
+ // a second Operator writer.
14
+ const active = sessions.sessions[activeAgentId];
15
+ const matchingActive = active?.adapterId === activeAdapterId ? active : undefined;
16
+ const activeRef = matchingActive === undefined ? undefined : operatorSessionRef(matchingActive);
17
+ const historicalConversations = listOperatorSessions(sessions)
18
+ .filter((entry) => entry.ref !== activeRef)
19
+ .map((entry) => ({ ...entry, state: "history" }));
20
+ if (matchingActive === undefined) {
21
+ return Object.freeze({
22
+ writer: { state: "unrecorded", agentId: activeAgentId, adapterId: activeAdapterId },
23
+ historicalConversations
24
+ });
25
+ }
26
+ return Object.freeze({
27
+ writer: {
28
+ state: matchingActive.status === "stopped" || matchingActive.status === "broken" ? "inactive" : "active",
29
+ agentId: matchingActive.agentId,
30
+ adapterId: matchingActive.adapterId,
31
+ sessionRef: activeRef,
32
+ nativeSessionId: matchingActive.nativeSessionId,
33
+ sessionStatus: matchingActive.status
34
+ },
35
+ historicalConversations
36
+ });
37
+ }
2
38
  export function operatorSessionRef(session) {
3
39
  return roleAgentSessionRef(session);
4
40
  }
@@ -13,17 +13,19 @@
13
13
  * the candidate read-only and reports dual-owner; a crashed activator resumes
14
14
  * from the recorded phase.
15
15
  */
16
+ import { RELEASE_HANDOVER_PROMOTION_TIMEOUT_MS } from "../runtime/runtimeDeadlines.js";
16
17
  import { acquireHandoverLock, isOwnerLive, newHandoverId, readActiveReleasePointer, readCandidateDiscovery, readHandoverFence, readHandoverReceipt, readRuntimeIdentity, removeCandidateDiscovery, removeHandoverFence, writeActiveReleasePointer, writeHandoverFence, writeHandoverReceipt } from "./runtimeRelease.js";
17
18
  export const DEFAULT_CANDIDATE_READY_TIMEOUT_MS = 30_000;
18
- export const DEFAULT_PROMOTION_TIMEOUT_MS = 45_000;
19
+ export const DEFAULT_PROMOTION_TIMEOUT_MS = RELEASE_HANDOVER_PROMOTION_TIMEOUT_MS;
19
20
  export const DEFAULT_POLL_INTERVAL_MS = 100;
20
21
  /**
21
22
  * Optional confirmation debounce after the candidate latches `dualOwner:
22
23
  * true`. Defaults to 0: the candidate's own exit grace
23
- * (`DEFAULT_DUAL_OWNER_GRACE_MS` in `handoverCandidate.ts`, 30s) is the single
24
- * authoritative old-owner exit window, and the activator trusts the candidate's
25
- * latched signal. A non-zero value only adds a short extra confirmation before
26
- * reporting dual-owner; it must never be used to re-litigate the exit grace.
24
+ * (`DEFAULT_DUAL_OWNER_GRACE_MS` in `handoverCandidate.ts`) is the single
25
+ * authoritative old-owner exit window. It outlives the complete Controller
26
+ * shutdown/drain boundary, and the activator trusts the candidate's latched
27
+ * signal. A non-zero value only adds a short extra confirmation before reporting
28
+ * dual-owner; it must never be used to re-litigate the exit grace.
27
29
  */
28
30
  export const DEFAULT_DUAL_OWNER_GRACE_MS = 0;
29
31
  export async function activateRelease(ports, options) {
@@ -251,6 +251,21 @@ export function writeCandidateDiscovery(home, candidate) {
251
251
  export function removeCandidateDiscovery(home) {
252
252
  rmSync(join(resolve(home), CANDIDATE_DISCOVERY_PATH), { force: true });
253
253
  }
254
+ /**
255
+ * Read-only scheduler fence for the short release/rebind critical section.
256
+ * A stale owner does not block work; an unreadable lock fails closed for
257
+ * bounded Operator diagnosis instead of dispatching across an unknown fence.
258
+ */
259
+ export function isHandoverLockHeld(home) {
260
+ const lockPath = join(resolve(home), "runtime", "handover.lock");
261
+ try {
262
+ const owner = JSON.parse(readFileSync(lockPath, "utf8"));
263
+ return isHandoverLockLive(owner);
264
+ }
265
+ catch (error) {
266
+ return !isEnoent(error);
267
+ }
268
+ }
254
269
  export function acquireHandoverLock(home) {
255
270
  const lockPath = join(resolve(home), "runtime", "handover.lock");
256
271
  mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 });
@@ -108,6 +108,14 @@ export class TaskWorkspaceCoordinator {
108
108
  await this.#stopLiveRoles(item.taskId, this.#workItemRoleNames(item));
109
109
  return "released";
110
110
  }
111
+ /**
112
+ * Stops one Task Role's physical runtime without changing its workspace.
113
+ * The caller owns the subsequent atomic record retirement and wake.
114
+ */
115
+ async cleanupTaskRoleRuntime(taskId, roleName) {
116
+ await this.#stopLiveRoles(taskId, [roleName]);
117
+ return "released";
118
+ }
111
119
  async cleanupReviewRound(taskId, reviewRoundId) {
112
120
  const round = this.store.getReviewRound(taskId, reviewRoundId);
113
121
  if (round === null)
@@ -384,17 +392,12 @@ export class TaskWorkspaceCoordinator {
384
392
  const observedPanes = inspect?.(taskId);
385
393
  const live = targets.filter((roleName) => {
386
394
  const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
387
- const activeSession = sessions === null
388
- ? undefined
389
- : sessions.activeAgentId === undefined
390
- // Narrow test doubles and restored callers predating activeAgentId
391
- // still conservatively represent any nonterminal record as live.
392
- ? Object.values(sessions.sessions).find(({ status }) => status !== "stopped" && status !== "broken")
393
- : sessions.sessions[sessions.activeAgentId];
394
395
  return observedPanes?.some((pane) => pane.roleName === roleName && !pane.dead) === true
395
- || (sessions !== null && activeSession !== undefined
396
- && activeSession.status !== "stopped"
397
- && activeSession.status !== "broken");
396
+ // A terminal current Session can still carry a resumable native id or
397
+ // Provider binding. Exact cleanup retires both before a workspace or
398
+ // release-control transition is allowed to wake this Role again.
399
+ || (sessions !== null && (Object.keys(sessions.sessions).length > 0
400
+ || sessions.providerBinding !== null));
398
401
  });
399
402
  if (live.length > 0)
400
403
  await this.runtime.stopTaskRoleSessions(taskId, live);
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { deltaRecheckMaxChangedFiles, deltaRecheckMaxChangedLines } from "./reviewConfig.js";
3
3
  import { validateDeltaRecheckRecord } from "./reviewRound.js";
4
+ import { isSemanticReviewRound } from "./reviewOutcomeClassifier.js";
4
5
  /**
5
6
  * Assesses whether a delta-recheck may be attempted. Every deterministic
6
7
  * gate fails closed here; semantic equivalence is always left to the
@@ -8,11 +9,11 @@ import { validateDeltaRecheckRecord } from "./reviewRound.js";
8
9
  */
9
10
  export async function assessDeltaRecheck(input) {
10
11
  const { repositoryPaths, previousRound, candidate, git, config } = input;
11
- if (previousRound.status !== "completed"
12
+ if (!isSemanticReviewRound(previousRound)
12
13
  || (previousRound.scope ?? "work-item") !== "task") {
13
14
  return {
14
15
  kind: "ineligible",
15
- reason: "Delta recheck requires a completed Task-final ReviewRound."
16
+ reason: "Delta recheck requires a semantic completed Task-final ReviewRound."
16
17
  };
17
18
  }
18
19
  if (previousRound.taskCandidate === undefined) {
@@ -117,11 +117,11 @@ export function reconcileReviewFindings(store, taskId, roundId, now) {
117
117
  if (round === null) {
118
118
  return { roundId, skipped: true, reason: "ReviewRound not found.", created: [], updated: [], conflicts: [] };
119
119
  }
120
- if (!isSemanticReviewRound(round)) {
120
+ if (!isSemanticReviewRound(round, store)) {
121
121
  return {
122
122
  roundId,
123
123
  skipped: true,
124
- reason: "ReviewRound is an execution-attempt failure, not a semantic report.",
124
+ reason: "ReviewRound is non-semantic or ambiguous, not a proven semantic report.",
125
125
  created: [],
126
126
  updated: [],
127
127
  conflicts: []
@@ -464,7 +464,7 @@ export function renderFindingLedgerContext(summary) {
464
464
  export function reusableTaskReviewEvidence(store, taskId, candidate) {
465
465
  const rounds = store.listReviewRounds(taskId)
466
466
  .filter((round) => (round.scope ?? "work-item") === "task"
467
- && round.status === "completed"
467
+ && isSemanticReviewRound(round, store)
468
468
  && round.evidenceCommit !== undefined
469
469
  && isSameTaskReviewCandidate(round.taskCandidate, candidate))
470
470
  .sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
@@ -492,7 +492,8 @@ export function reusableTaskReviewEvidence(store, taskId, candidate) {
492
492
  */
493
493
  export function buildTaskFinalReviewFindingContext(store, taskId, candidate) {
494
494
  const previousSemanticRound = store.listReviewRounds(taskId)
495
- .filter((round) => (round.scope ?? "work-item") === "task" && round.status === "completed")
495
+ .filter((round) => (round.scope ?? "work-item") === "task"
496
+ && isSemanticReviewRound(round, store))
496
497
  .sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }))
497
498
  .at(-1) ?? null;
498
499
  const reusableEvidence = reusableTaskReviewEvidence(store, taskId, candidate);
@@ -1,61 +1,270 @@
1
+ import { matchYieldReceipt } from "../run/yieldReceipt.js";
2
+ import { isTaskRecordRetired, operationalTaskRecords } from "../task/taskRecordRetirement.js";
1
3
  const INFRA_SIGNATURES = [
2
- {
3
- kind: "session-not-stopped",
4
- pattern: /session must be stopped before workspace migration/iu
5
- },
6
- {
7
- kind: "run-start",
8
- pattern: /role run could not start|could not start (?:the )?(?:reviewer|role) run/iu
9
- },
10
- {
11
- kind: "storage-lock",
12
- pattern: /\.state\.lock|state lock|lock timeout|storage lock/iu
13
- },
14
- {
15
- kind: "tmux-exit",
16
- pattern: /tmux[^\n]{0,80}(?:exited|exit|died|vanished)|pane (?:exited|died)/iu
17
- },
18
- {
19
- kind: "yield-timeout",
20
- pattern: /yield timeout|controller yield timeout|yield timed out/iu
21
- },
22
- {
23
- kind: "run-identity",
24
- pattern: /wrong run id|unknown run id|run id (?:is )?(?:invalid|unknown|mismatch)/iu
25
- },
26
- {
27
- kind: "policy",
28
- pattern: /cyber_?policy|policy denial|permission denied by policy/iu
29
- },
30
- {
31
- kind: "baseline-contamination",
32
- pattern: /cross[-\s]?baseline|baseline pollution|contaminated baseline|wrong base sha/iu
33
- }
4
+ { kind: "session-not-stopped", pattern: /session must be stopped before workspace migration/iu },
5
+ { kind: "run-start", pattern: /role run could not start|could not start (?:the )?(?:reviewer|role) run/iu },
6
+ { kind: "storage-lock", pattern: /\.state\.lock|state lock|lock timeout|storage lock/iu },
7
+ { kind: "tmux-exit", pattern: /tmux[^\n]{0,80}(?:exited|exit|died|vanished)|pane (?:exited|died)/iu },
8
+ { kind: "yield-timeout", pattern: /yield timeout|controller yield timeout|yield timed out/iu },
9
+ { kind: "run-identity", pattern: /wrong run id|unknown run id|run id (?:is )?(?:invalid|unknown|mismatch)/iu },
10
+ { kind: "policy", pattern: /cyber_?policy|policy denial|permission denied by policy/iu },
11
+ { kind: "baseline-contamination", pattern: /cross[-\s]?baseline|baseline pollution|contaminated baseline|wrong base sha/iu },
12
+ { kind: "context-load", pattern: /(?:run )?(?:context|context pack) load (?:failed|unavailable|unauthorized|stale|mismatched|malformed)/iu },
13
+ { kind: "workspace-binding", pattern: /workspace is not the durable owner|workspace-binding failure/iu }
34
14
  ];
35
- /**
36
- * Classifies a terminal Round. `completed` is always semantic (the reviewer
37
- * delivered a report); `failed` is always infra. Non-terminal Rounds have no
38
- * outcome yet.
39
- */
40
- export function classifyReviewRoundOutcome(round) {
41
- if (round.status === "completed") {
42
- return { kind: "semantic", reason: "Reviewer delivered a report." };
43
- }
44
- if (round.status !== "failed")
15
+ /** Classify one terminal Round without rewriting it. */
16
+ export function classifyReviewRoundOutcome(round, evidence) {
17
+ if (round.status !== "completed" && round.status !== "failed")
45
18
  return null;
46
- const text = `${round.summary ?? ""}\n${round.report ?? ""}`;
47
- for (const { kind, pattern } of INFRA_SIGNATURES) {
48
- if (pattern.test(text)) {
49
- return { kind: "infra", infraKind: kind, reason: `Review execution attempt failed (${kind}).` };
19
+ if (evidence !== undefined && round.reviewerRunId !== undefined
20
+ && isTaskRecordRetired(evidence.listEvents(round.taskId), "agent-run", round.reviewerRunId)) {
21
+ return {
22
+ kind: "non-semantic",
23
+ infraKind: "run-identity",
24
+ reason: `Reviewer Run ${round.reviewerRunId} was retired from operational evidence.`
25
+ };
26
+ }
27
+ const infraKind = classifyInfraKind(`${round.summary ?? ""}\n${round.report ?? ""}`);
28
+ if (round.status === "failed") {
29
+ const semanticEvidence = failedRoundSemanticEvidence(round, evidence);
30
+ return semanticEvidence === null
31
+ ? {
32
+ kind: "non-semantic",
33
+ infraKind,
34
+ reason: "Failed Review execution carries no semantic report evidence."
35
+ }
36
+ : {
37
+ kind: "ambiguous",
38
+ infraKind,
39
+ reason: semanticEvidence
40
+ };
41
+ }
42
+ if (!explicitCompletedReviewInfrastructureFailure(round.summary ?? "", round)) {
43
+ return { kind: "semantic", reason: "Reviewer delivered a terminal report." };
44
+ }
45
+ if (evidence === undefined) {
46
+ return {
47
+ kind: "ambiguous",
48
+ infraKind,
49
+ reason: "Completed Round claims an infrastructure failure but corroborating Run/Event evidence was not supplied."
50
+ };
51
+ }
52
+ const corroborationFailure = completedInfrastructureCorroborationFailure(round, evidence);
53
+ return corroborationFailure === null
54
+ ? {
55
+ kind: "non-semantic",
56
+ infraKind,
57
+ reason: "Completed Round and yielded Run agree on an explicit pre-review infrastructure failure."
50
58
  }
59
+ : {
60
+ kind: "ambiguous",
61
+ infraKind,
62
+ reason: corroborationFailure
63
+ };
64
+ }
65
+ /** True only when a Round may consume semantic Review budget or feed findings. */
66
+ export function isSemanticReviewRound(round, evidence) {
67
+ return classifyReviewRoundOutcome(round, evidence)?.kind === "semantic";
68
+ }
69
+ function classifyInfraKind(text) {
70
+ return INFRA_SIGNATURES.find(({ pattern }) => pattern.test(text))?.kind ?? "other-infra";
71
+ }
72
+ function failedRoundSemanticEvidence(round, evidence) {
73
+ if ((round.checks ?? []).length > 0)
74
+ return "Failed Round records review checks.";
75
+ if (round.evidenceCommit !== undefined)
76
+ return "Failed Round records a review evidence commit.";
77
+ if (round.deltaRecheck?.disposition !== undefined || round.deltaRecheck?.reasoning !== undefined) {
78
+ return "Failed Round records a semantic delta-recheck disposition.";
79
+ }
80
+ if (round.report !== round.summary)
81
+ return "Failed Round stores a report distinct from its terminal summary.";
82
+ if (runtimeFailureSummaryHasReviewerOutput(round.report ?? "")) {
83
+ return "Failed Round stores non-empty Reviewer output in its runtime failure summary.";
51
84
  }
52
- return {
53
- kind: "infra",
54
- infraKind: "other-infra",
55
- reason: "Review execution attempt failed before a semantic report."
56
- };
85
+ if (looksLikeStructuredReviewReport(round.report ?? "")) {
86
+ return "Failed Round stores a structured Reviewer report.";
87
+ }
88
+ const semanticLane = (round.executionGroup?.lanes ?? []).find((lane) => ((lane.result?.checks ?? []).length > 0
89
+ || (lane.result?.findings ?? []).length > 0
90
+ || (lane.result?.evidence ?? []).length > 0
91
+ || lane.result?.evidenceCommit !== undefined
92
+ || lane.result?.gitSnapshot !== undefined
93
+ || lane.status === "yielded"
94
+ || lane.status === "completed"));
95
+ if (semanticLane !== undefined)
96
+ return `Reviewer Lane ${semanticLane.id} delivered semantic evidence.`;
97
+ if (evidence !== undefined) {
98
+ const events = evidence.listEvents(round.taskId);
99
+ const reviewRun = operationalTaskRecords(evidence.listAgentRuns(round.taskId), events, "agent-run").find((run) => (run.purpose === "review"
100
+ && run.reviewRoundId === round.id
101
+ && (run.status === "yielded" || runtimeFailureSummaryHasReviewerOutput(run.summary ?? ""))));
102
+ if (reviewRun !== undefined)
103
+ return `Reviewer Run ${reviewRun.id} records Reviewer output.`;
104
+ const finding = evidence.listReviewFindings(round.taskId).find((entry) => (entry.firstReviewRoundId === round.id || entry.lastReviewRoundId === round.id));
105
+ if (finding !== undefined)
106
+ return `Review finding ${finding.id} references the Round.`;
107
+ const completion = events.find((event) => (event.type === "review.completed" && event.payload.reviewRoundId === round.id));
108
+ if (completion !== undefined)
109
+ return `Review completion Event ${completion.id} exists.`;
110
+ }
111
+ return null;
57
112
  }
58
- /** True when this Round may feed the finding ledger. */
59
- export function isSemanticReviewRound(round) {
60
- return classifyReviewRoundOutcome(round)?.kind === "semantic";
113
+ function completedInfrastructureCorroborationFailure(round, store) {
114
+ if ((round.checks ?? []).length > 0)
115
+ return "Completed Round records review checks.";
116
+ if (round.evidenceCommit !== round.reviewBaseCommit) {
117
+ return "Completed Round lacks an exact frozen-head evidence commit.";
118
+ }
119
+ if (round.report !== round.summary) {
120
+ return "Completed Round stores a report distinct from its terminal summary.";
121
+ }
122
+ if (runtimeFailureSummaryHasReviewerOutput(round.report ?? "")) {
123
+ return "Completed Round stores non-empty Reviewer output in its runtime failure summary.";
124
+ }
125
+ if (round.deltaRecheck?.disposition !== undefined || round.deltaRecheck?.reasoning !== undefined) {
126
+ return "Completed Round records a semantic delta-recheck disposition.";
127
+ }
128
+ if (looksLikeStructuredReviewReport(round.report ?? "")) {
129
+ return "Completed Round stores a structured Reviewer report.";
130
+ }
131
+ for (const lane of round.executionGroup?.lanes ?? []) {
132
+ if (lane.status === "pending" || lane.status === "running") {
133
+ return `Reviewer Lane ${lane.id} is still active.`;
134
+ }
135
+ if ((lane.result?.checks ?? []).length > 0
136
+ || (lane.result?.findings ?? []).length > 0
137
+ || (lane.result?.evidence ?? []).length > 0
138
+ || (lane.result?.evidenceCommit !== undefined
139
+ && lane.result.evidenceCommit !== round.reviewBaseCommit)
140
+ || lane.result?.gitSnapshot !== undefined) {
141
+ return `Reviewer Lane ${lane.id} delivered semantic evidence.`;
142
+ }
143
+ if (lane.status === "yielded" || lane.status === "completed") {
144
+ if (lane.result === undefined
145
+ || lane.result.summary !== round.summary
146
+ || lane.result.report !== round.report
147
+ || lane.result.evidenceCommit !== round.reviewBaseCommit) {
148
+ return `Reviewer Lane ${lane.id} output is absent or differs from the Round.`;
149
+ }
150
+ }
151
+ else {
152
+ return `Completed Round has non-completed Reviewer Lane ${lane.id}/${lane.status}.`;
153
+ }
154
+ }
155
+ const allEvents = store.listEvents(round.taskId);
156
+ const runs = operationalTaskRecords(store.listAgentRuns(round.taskId), allEvents, "agent-run").filter((run) => (run.purpose === "review" && run.reviewRoundId === round.id));
157
+ const active = runs.find(({ status }) => status === "active");
158
+ if (active !== undefined)
159
+ return `Reviewer Run ${active.id} is still active.`;
160
+ const output = runs.find((run) => (run.status === "failed" && runtimeFailureSummaryHasReviewerOutput(run.summary ?? "")));
161
+ if (output !== undefined)
162
+ return `Reviewer Run ${output.id} records Reviewer output.`;
163
+ const yielded = runs.filter(({ status }) => status === "yielded");
164
+ if (round.reviewerRunId === undefined
165
+ || yielded.length !== 1
166
+ || yielded[0].id !== round.reviewerRunId) {
167
+ return "Completed Round lacks one exact yielded Reviewer Run.";
168
+ }
169
+ const run = yielded[0];
170
+ if (run.roleName !== round.reviewerRoleName
171
+ || run.summary !== round.summary
172
+ || run.yieldReceipt === undefined) {
173
+ return `Reviewer Run ${run.id} does not match the non-semantic Round receipt.`;
174
+ }
175
+ const receiptMatch = matchYieldReceipt(run.yieldReceipt, {
176
+ status: "yielded",
177
+ summary: round.summary ?? "",
178
+ reviewResult: {
179
+ report: round.report ?? "",
180
+ checks: round.checks ?? [],
181
+ evidenceCommit: round.reviewBaseCommit
182
+ }
183
+ });
184
+ if (receiptMatch?.kind !== "replayed") {
185
+ return `Reviewer Run ${run.id} yield receipt does not cover the Round outcome.`;
186
+ }
187
+ const finding = store.listReviewFindings(round.taskId).find((entry) => (entry.firstReviewRoundId === round.id || entry.lastReviewRoundId === round.id));
188
+ if (finding !== undefined)
189
+ return `Review finding ${finding.id} references the Round.`;
190
+ const events = allEvents.filter((event) => (event.type === "review.completed" && event.payload.reviewRoundId === round.id));
191
+ if (events.length !== 1)
192
+ return "Completed Round lacks one exact completion Event.";
193
+ const event = events[0];
194
+ if (event.payload.workItemId !== round.workItemId
195
+ || event.payload.candidateId !== round.candidateId
196
+ || event.payload.reviewBaseCommit !== round.reviewBaseCommit
197
+ || event.payload.evidenceCommit !== round.reviewBaseCommit
198
+ || event.payload.checks !== "none") {
199
+ return `Review completion Event ${event.id} carries mismatched or semantic evidence.`;
200
+ }
201
+ return null;
202
+ }
203
+ function explicitCompletedReviewInfrastructureFailure(summary, round) {
204
+ if (exactCompletedReviewInfrastructureFailureReport(summary))
205
+ return true;
206
+ const report = summary.trim();
207
+ if (report === `Role Run workspace is not the durable owner: ${round.taskId}/${round.reviewerRoleName}.`
208
+ || (round.reviewerRunId !== undefined
209
+ && report === `Review Run workspace is not the durable owner: ${round.reviewerRunId}.`)) {
210
+ return true;
211
+ }
212
+ return /^(?:Run )?(?:Context|Context Pack) load (?:failed|unavailable|unauthorized|stale|mismatched|malformed)(?:: (?:failure|mismatch|unavailable|unauthorized|stale|mismatched|malformed))?\.?$/iu
213
+ .test(report);
214
+ }
215
+ function exactCompletedReviewInfrastructureFailureReport(summary) {
216
+ const lines = summary.trim().split(/\r?\n/u);
217
+ const envelope = [
218
+ /^# Review result: (?:context-load|workspace-binding) failure$/u,
219
+ /^$/u,
220
+ /^The assigned Run context could not be safely matched to this native session, so no candidate review was performed\.$/u,
221
+ /^$/u,
222
+ /^- Run: `[^`\r\n]+`$/u,
223
+ /^- ReviewRound: `[^`\r\n]+`$/u,
224
+ /^- Review base commit: `[0-9a-f]{40}`$/u,
225
+ /^- Frozen target: `[^`\r\n]+`$/u,
226
+ /^- Authorized workspace from the exact Context Pack: `[^`\r\n]+`$/u,
227
+ /^- Session-attached workspace: `[^`\r\n]+`$/u,
228
+ /^- Verification: both paths resolve distinctly and have different filesystem inodes \(`[^`\r\n]+` vs `[^`\r\n]+`\)\.$/u,
229
+ /^$/u,
230
+ /^## Findings$/u,
231
+ /^$/u,
232
+ /^- Verified-fixed findings: none; review did not start\.$/u,
233
+ /^- New findings: none; candidate sources were intentionally not inspected\.$/u,
234
+ /^- Accepted risks: none accepted\.$/u,
235
+ /^- Residual verification gaps: the complete frozen diff, changed control-flow paths, callers, data-integrity behavior, and required deterministic checks remain unreviewed because the Review workspace binding is mismatched\.$/u,
236
+ /^$/u,
237
+ /^## Checks actually run$/u,
238
+ /^$/u,
239
+ /^- Exact Context API load: passed for Task\/Run\/Role\/purpose\/subject\/snapshot\/adapter\.$/u,
240
+ /^- Workspace binding verification: failed\.$/u,
241
+ /^- Candidate build\/tests\/package checks: not run\.$/u,
242
+ /^- Real-provider E2E: not run and not authorized\.$/u,
243
+ /^$/u,
244
+ /^## Required next action$/u,
245
+ /^$/u,
246
+ /^Attach the native Reviewer session to the exact workspace recorded by the Context Pack, or issue a fresh internally consistent Review Run\/Context Pack\. Then perform the complete bounded Task-final review at the frozen head\.$/u
247
+ ];
248
+ return lines.length === envelope.length
249
+ && envelope.every((pattern, index) => pattern.test(lines[index]));
250
+ }
251
+ function runtimeFailureSummaryHasReviewerOutput(summary) {
252
+ const output = /(?:^|\n)last_assistant_message:[ \t]*([\s\S]*)$/u.exec(summary)?.[1];
253
+ return output !== undefined && output.trim().length > 0;
254
+ }
255
+ function looksLikeStructuredReviewReport(report) {
256
+ let parsed;
257
+ try {
258
+ parsed = JSON.parse(report);
259
+ }
260
+ catch {
261
+ return false;
262
+ }
263
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
264
+ return false;
265
+ const record = parsed;
266
+ return [
267
+ "summary", "report", "checks", "findings", "evidence", "evidenceCommit",
268
+ "deltaDisposition", "deltaReasoning"
269
+ ].some((key) => Object.hasOwn(record, key));
61
270
  }
@@ -0,0 +1 @@
1
+ export const TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT = "review.task-final-contract-rebound";