@zq-silk/yui 0.14.1 → 0.15.0

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 (185) hide show
  1. package/ARCHITECTURE.md +91 -47
  2. package/README.md +275 -145
  3. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  4. package/dist/cli/commandCatalog.js +74 -109
  5. package/dist/cli/helpRenderer.js +1 -2
  6. package/dist/cli/interactionCandidates.js +10 -0
  7. package/dist/cli/interactionPolicy.js +76 -37
  8. package/dist/cli/roleWizard.js +5 -6
  9. package/dist/cli/updateCommand.js +17 -9
  10. package/dist/cli/updateOrchestrator.js +87 -19
  11. package/dist/cli/updatePorts.js +79 -13
  12. package/dist/cli/upgradeCommand.js +105 -23
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/controllerCommands.js +1 -1
  18. package/dist/commands/durableJobCommands.js +1 -1
  19. package/dist/commands/executionAuditCommands.js +4 -9
  20. package/dist/commands/globalRoleCommands.js +17 -0
  21. package/dist/commands/profileCommands.js +200 -30
  22. package/dist/commands/sessionCommands.js +1 -1
  23. package/dist/commands/taskActor.js +29 -127
  24. package/dist/commands/taskChangeSetCommands.js +1 -3
  25. package/dist/commands/taskCommands.js +1771 -2107
  26. package/dist/commands/taskCompletionGate.js +27 -103
  27. package/dist/commands/taskContextCommand.js +47 -80
  28. package/dist/commands/taskExecutionCommands.js +9 -27
  29. package/dist/commands/taskInputCommands.js +10 -10
  30. package/dist/commands/taskIntegrationCommands.js +69 -61
  31. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  32. package/dist/commands/taskNextActionCommand.js +8 -29
  33. package/dist/commands/taskOverviewCommand.js +25 -3
  34. package/dist/commands/taskPublicationCommands.js +151 -85
  35. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  36. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  37. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  38. package/dist/commands/taskUpstreamCommands.js +79 -95
  39. package/dist/config/configCatalog.js +1 -1
  40. package/dist/context/contextSnapshot.js +17 -2
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +6 -5
  63. package/dist/doctor/doctor.js +37 -14
  64. package/dist/domain/agentResultTransport.js +60 -0
  65. package/dist/execution/executionHealth.js +111 -118
  66. package/dist/execution/reviewMainTurn.js +161 -0
  67. package/dist/execution/workItemExecution.js +454 -0
  68. package/dist/execution/workItemExecutionProjection.js +309 -0
  69. package/dist/execution/workItemMainTurn.js +164 -0
  70. package/dist/executor/agentExecutor.js +19 -19
  71. package/dist/executor/executorRegistry.js +7 -28
  72. package/dist/executor/fileRoleLaunchPlanner.js +16 -82
  73. package/dist/external/githubPublicationVerifier.js +114 -0
  74. package/dist/external/gitlabPublicationVerifier.js +158 -0
  75. package/dist/external/pinnedCommandRunner.js +73 -0
  76. package/dist/integration/changeSetManifest.js +0 -3
  77. package/dist/integration/deliveryObligation.js +43 -6
  78. package/dist/integration/gitIntegrationService.js +284 -101
  79. package/dist/integration/integrationAttempt.js +64 -14
  80. package/dist/integration/integrationQueueEntry.js +5 -35
  81. package/dist/integration/integrationQueueService.js +65 -194
  82. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  83. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  84. package/dist/message/message.js +16 -0
  85. package/dist/observability/executionAudit.js +4 -25
  86. package/dist/observability/faultClassification.js +58 -70
  87. package/dist/observability/orchestrationMetrics.js +8 -50
  88. package/dist/observability/runtimeIdentity.js +48 -50
  89. package/dist/profile/agentProfile.js +60 -15
  90. package/dist/profile/agentProfileRuntime.js +100 -0
  91. package/dist/release/releaseWorkflowPorts.js +5 -68
  92. package/dist/release/runtimeRelease.js +9 -1
  93. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  94. package/dist/repository/gitWorkspace.js +9 -67
  95. package/dist/repository/taskBaseFreshness.js +3 -1
  96. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  97. package/dist/repository/taskWorkspacePreparer.js +252 -411
  98. package/dist/resources/liveReferences.js +6 -55
  99. package/dist/resources/resourceDiscovery.js +1 -30
  100. package/dist/resources/resourceRegistrar.js +0 -23
  101. package/dist/review/deltaRecheck.js +8 -65
  102. package/dist/review/reviewAcceptance.js +22 -19
  103. package/dist/review/reviewConfig.js +0 -10
  104. package/dist/review/reviewDecision.js +2 -2
  105. package/dist/review/reviewRound.js +147 -273
  106. package/dist/review/reviewerAvailability.js +13 -23
  107. package/dist/role/role.js +3 -0
  108. package/dist/runtime/agentDriverObservation.js +1 -1
  109. package/dist/runtime/agentHost.js +51 -51
  110. package/dist/runtime/builtinAgentDrivers.js +9 -2
  111. package/dist/runtime/codexAppServerRuntime.js +23 -5
  112. package/dist/runtime/exactControlPlane.js +22 -296
  113. package/dist/runtime/firstProgressAdvisory.js +1 -1
  114. package/dist/runtime/index.js +1 -1
  115. package/dist/runtime/launchBroker.js +11 -11
  116. package/dist/runtime/lifecycleReservation.js +2 -2
  117. package/dist/runtime/managedCaller.js +111 -0
  118. package/dist/runtime/ports.js +16 -3
  119. package/dist/runtime/preallocatedNativeSession.js +2 -2
  120. package/dist/runtime/processExitObservation.js +3 -2
  121. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  122. package/dist/runtime/runtimeBinding.js +1 -1
  123. package/dist/runtime/runtimeObservation.js +33 -16
  124. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  125. package/dist/runtime/runtimeStopReceipt.js +11 -11
  126. package/dist/runtime/sessionLaunchRequest.js +2 -2
  127. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  128. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  129. package/dist/runtime/sessionReconciliation.js +4 -4
  130. package/dist/runtime/sessionTerminationGuard.js +9 -9
  131. package/dist/runtime/sessionTokenMetrics.js +5 -7
  132. package/dist/runtime/structuredProviderHost.js +5 -5
  133. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  134. package/dist/runtime/tmuxAdapters.js +32 -30
  135. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  136. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  137. package/dist/scheduler/roleTurnLiveness.js +6 -6
  138. package/dist/scheduler/roleTurnStall.js +8 -9
  139. package/dist/scheduler/taskExecutionProjection.js +19 -41
  140. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  141. package/dist/storage/currentTaskStore.js +6 -4
  142. package/dist/storage/sqliteSchema.js +135 -80
  143. package/dist/storage/sqliteStore.js +69 -52
  144. package/dist/storage/storageSchema.js +92 -230
  145. package/dist/storage/storageVersions.js +12 -13
  146. package/dist/storage/taskStore.js +8 -8
  147. package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
  148. package/dist/task/completionReadiness.js +17 -98
  149. package/dist/task/deliveryGuard.js +9 -11
  150. package/dist/task/draftPlan.js +142 -0
  151. package/dist/task/nextAction.js +179 -477
  152. package/dist/task/publicationVerification.js +1 -0
  153. package/dist/task/remoteDelivery.js +233 -0
  154. package/dist/task/task.js +108 -4
  155. package/dist/task/taskRecordReference.js +0 -1
  156. package/dist/turn/turn.js +109 -16
  157. package/dist/verification/verificationGateService.js +0 -26
  158. package/dist/version.js +3 -3
  159. package/dist/web/assets/client/components.js +99 -121
  160. package/dist/web/assets/client/i18n.js +136 -58
  161. package/dist/web/assets/client/view.js +44 -0
  162. package/dist/web/assets/styles/cards.js +3 -15
  163. package/dist/web/assets/styles/widgets.js +4 -0
  164. package/dist/web/webSnapshot.js +29 -14
  165. package/dist/workItem/dependencyGate.js +34 -0
  166. package/dist/workItem/workItem.js +99 -266
  167. package/dist/workspace/workItemChangeSetManager.js +15 -59
  168. package/docs/task-local-identity.md +9 -9
  169. package/i18n/README.zh-CN.md +189 -74
  170. package/package.json +1 -1
  171. package/skills/yui-leader/SKILL.md +272 -782
  172. package/skills/yui-operator/SKILL.md +190 -365
  173. package/skills/yui-reviewer/SKILL.md +40 -33
  174. package/skills/yui-runtime/SKILL.md +22 -0
  175. package/skills/yui-worker/SKILL.md +19 -6
  176. package/dist/execution/candidateConvergence.js +0 -623
  177. package/dist/execution/executionGroup.js +0 -854
  178. package/dist/execution/resourceBroker.js +0 -426
  179. package/dist/executor/launchPlan.js +0 -16
  180. package/dist/review/reviewFinding.js +0 -240
  181. package/dist/review/reviewFindingLedger.js +0 -550
  182. package/dist/review/reviewOutcomeClassifier.js +0 -255
  183. package/dist/scheduler/resourceQueueProjection.js +0 -71
  184. package/dist/storage/upgrade/recordVersions.js +0 -82
  185. package/dist/task/repairWave.js +0 -150
@@ -1,16 +1,13 @@
1
- import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
2
1
  export const FAULT_CLASSES = [
3
- "provider-transient",
4
- "policy-denied",
5
2
  "session-dead",
6
3
  "delivery-uncertain",
7
- "storage-backend-lock",
4
+ "result-missing",
5
+ "runtime-failure",
6
+ "workspace-state",
8
7
  "review-infra",
9
- "review-semantic-negative",
10
8
  "integration-environment",
11
9
  "integration-candidate-failure",
12
10
  "stale-base-target-cas",
13
- "archive-resource-leak",
14
11
  "other"
15
12
  ];
16
13
  export const NO_FAULT = Object.freeze({
@@ -18,87 +15,76 @@ export const NO_FAULT = Object.freeze({
18
15
  basis: "none",
19
16
  evidence: ""
20
17
  });
21
- const PROVIDER_TRANSIENT_PATTERN = /\b5\d{2}\b|gateway time|connection lost|server error|overloaded|rate.?limit|ECONNRESET|socket hang up|API Error|nonstream call error|mid-response/iu;
22
- const POLICY_DENIED_PATTERN = /policy|permission denied|forbidden|not authorized|\b403\b/iu;
23
- const SESSION_DEAD_PATTERN = /tmux session exited|session cannot be replaced|could not start|pane[^\n]*(dead|exited)|session[^\n]*(dead|exited|broken)|native session|launch reservation/iu;
24
- const STORAGE_LOCK_PATTERN = /storage lock|database is locked|SQLITE_BUSY|lock timeout|COMMAND_TIMED_OUT|storage conflict|timed out waiting/iu;
25
- const DELIVERY_UNCERTAIN_PATTERN = /delivery[^\n]*(uncertain|unknown)|uncertain[^\n]*delivery|push[^\n]*uncertain/iu;
26
- const STALE_BASE_PATTERN = /stale (role|agent|base|target|state)|expected head|base[^\n]*(changed|moved)|conflict|CAS/iu;
27
18
  const INTEGRATION_ENVIRONMENT_PATTERN = /tsc: not found|command not found|ENOENT|runner disappeared|runner vanished|dirty target|wrong argument|not a git repository|npm error|node: not found/iu;
28
19
  function excerpt(text, max = 160) {
29
20
  const normalized = text.replace(/\s+/g, " ").trim();
30
21
  return normalized.length <= max ? normalized : `${normalized.slice(0, max)}…`;
31
22
  }
32
- /**
33
- * Classify a failed Turn. A structured hint (from a future capability
34
- * provider) always wins; otherwise the summary is matched as historical
35
- * import text. Non-failed Turns return {@link NO_FAULT}.
36
- */
37
- export function classifyTurnFailure(run, structured) {
23
+ /** Classify a failed Turn without inspecting Agent-authored output. */
24
+ export function classifyTurnFailure(run) {
38
25
  if (run.status !== "failed")
39
26
  return NO_FAULT;
40
- if (structured !== undefined) {
27
+ const failureReason = run.result?.failureReason;
28
+ if (failureReason === "cancelled")
29
+ return NO_FAULT;
30
+ if (failureReason === "delivery-unknown") {
41
31
  return {
42
- faultClass: structured.faultClass,
43
- basis: "structured",
44
- evidence: structured.evidence
32
+ faultClass: "delivery-uncertain",
33
+ basis: "core-fact",
34
+ evidence: failureReason
45
35
  };
46
36
  }
47
- const summary = run.result?.output ?? "";
48
- if (summary.length === 0) {
49
- return { faultClass: "other", basis: "text-historical", evidence: "" };
50
- }
51
- const text = excerpt(summary);
52
- if (POLICY_DENIED_PATTERN.test(summary)) {
53
- return { faultClass: "policy-denied", basis: "text-historical", evidence: text };
54
- }
55
- if (PROVIDER_TRANSIENT_PATTERN.test(summary)) {
56
- return { faultClass: "provider-transient", basis: "text-historical", evidence: text };
57
- }
58
- if (STORAGE_LOCK_PATTERN.test(summary)) {
59
- return { faultClass: "storage-backend-lock", basis: "text-historical", evidence: text };
60
- }
61
- if (DELIVERY_UNCERTAIN_PATTERN.test(summary)) {
62
- return { faultClass: "delivery-uncertain", basis: "text-historical", evidence: text };
37
+ if (failureReason === "startup-failed") {
38
+ return {
39
+ faultClass: "session-dead",
40
+ basis: "core-fact",
41
+ evidence: failureReason
42
+ };
63
43
  }
64
- if (STALE_BASE_PATTERN.test(summary)) {
65
- return { faultClass: "stale-base-target-cas", basis: "text-historical", evidence: text };
44
+ if (failureReason === "missing-result") {
45
+ return {
46
+ faultClass: "result-missing",
47
+ basis: "core-fact",
48
+ evidence: failureReason
49
+ };
66
50
  }
67
- if (SESSION_DEAD_PATTERN.test(summary)) {
68
- return { faultClass: "session-dead", basis: "text-historical", evidence: text };
51
+ if (failureReason === "runtime-failed") {
52
+ return {
53
+ faultClass: "runtime-failure",
54
+ basis: "core-fact",
55
+ evidence: failureReason
56
+ };
69
57
  }
70
- return { faultClass: "other", basis: "text-historical", evidence: text };
71
- }
72
- /**
73
- * Review execution failure (the Round itself failed to execute/deliver) is
74
- * `review-infra`; a completed Round with failed checks is a semantic negative.
75
- */
76
- export function classifyReviewRound(round, evidence) {
77
- const outcome = classifyReviewRoundOutcome(round, evidence);
78
- if (outcome?.kind === "non-semantic") {
58
+ if (failureReason === "workspace-unavailable"
59
+ || failureReason === "workspace-dirty"
60
+ || failureReason === "workspace-branch-mismatch") {
79
61
  return {
80
- faultClass: "review-infra",
81
- basis: "structured",
82
- evidence: outcome.reason
62
+ faultClass: "workspace-state",
63
+ basis: "core-fact",
64
+ evidence: failureReason
83
65
  };
84
66
  }
85
- if (outcome?.kind === "semantic" && (round.checks ?? []).some((c) => c.outcome === "failed")) {
86
- const failed = (round.checks ?? [])
87
- .filter((c) => c.outcome === "failed")
88
- .map((c) => c.name)
89
- .join(",");
67
+ return {
68
+ faultClass: "other",
69
+ basis: "core-fact",
70
+ evidence: failureReason ?? "failed without Core failure reason"
71
+ };
72
+ }
73
+ /** ReviewRound failure is an execution fault; completed prose is not classified. */
74
+ export function classifyReviewRound(round) {
75
+ if (round.status === "failed") {
90
76
  return {
91
- faultClass: "review-semantic-negative",
92
- basis: "structured",
93
- evidence: `failed checks: ${failed}`
77
+ faultClass: "review-infra",
78
+ basis: "core-fact",
79
+ evidence: round.failure?.message ?? "Review execution failed without Core failure detail."
94
80
  };
95
81
  }
96
82
  return NO_FAULT;
97
83
  }
98
84
  /**
99
85
  * Integration failure classes: environment/toolchain failure, stale base/CAS
100
- * conflict, or candidate failure. Conflict reports are structured; check
101
- * details are matched as historical text.
86
+ * conflict, or candidate failure. Conflict/check records are Core-owned, but
87
+ * environment attribution still uses a regex over their diagnostic text.
102
88
  */
103
89
  export function classifyIntegrationAttempt(attempt) {
104
90
  if (attempt.status !== "failed")
@@ -106,7 +92,7 @@ export function classifyIntegrationAttempt(attempt) {
106
92
  if (attempt.conflict !== undefined) {
107
93
  return {
108
94
  faultClass: "stale-base-target-cas",
109
- basis: "structured",
95
+ basis: "core-fact",
110
96
  evidence: excerpt(attempt.conflict.summary)
111
97
  };
112
98
  }
@@ -116,20 +102,20 @@ export function classifyIntegrationAttempt(attempt) {
116
102
  if (INTEGRATION_ENVIRONMENT_PATTERN.test(checkText)) {
117
103
  return {
118
104
  faultClass: "integration-environment",
119
- basis: "text-historical",
105
+ basis: "core-diagnostic",
120
106
  evidence: excerpt(checkText)
121
107
  };
122
108
  }
123
109
  if ((attempt.checks ?? []).some((c) => c.outcome === "failed")) {
124
110
  return {
125
111
  faultClass: "integration-candidate-failure",
126
- basis: "structured",
112
+ basis: "core-fact",
127
113
  evidence: excerpt(checkText)
128
114
  };
129
115
  }
130
116
  return {
131
117
  faultClass: "other",
132
- basis: "structured",
118
+ basis: "core-fact",
133
119
  evidence: "failed without checks or conflict"
134
120
  };
135
121
  }
@@ -138,8 +124,10 @@ export function emptyFaultClassCounts() {
138
124
  }
139
125
  export function countFaultClasses(classifications) {
140
126
  const counts = new Map(FAULT_CLASSES.map((name) => [name, 0]));
141
- for (const { faultClass } of classifications) {
142
- counts.set(faultClass, (counts.get(faultClass) ?? 0) + 1);
127
+ for (const classification of classifications) {
128
+ if (classification.basis === "none")
129
+ continue;
130
+ counts.set(classification.faultClass, (counts.get(classification.faultClass) ?? 0) + 1);
143
131
  }
144
132
  return Object.freeze(Object.fromEntries(counts));
145
133
  }
@@ -1,19 +1,10 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
- import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
2
+ import { isCompletedReviewExecutionFromTurns } from "../review/reviewAcceptance.js";
3
3
  import { projectFirstProgressAdvisory } from "../runtime/firstProgressAdvisory.js";
4
4
  /** One Task's orchestration cost and advisory projection, with no writes. */
5
5
  export function projectTaskOrchestration(facts) {
6
- const evidence = {
7
- listTurns: () => facts.turns,
8
- listReviewFindings: () => facts.reviewFindings,
9
- listEvents: () => facts.events
10
- };
11
6
  const fullRounds = facts.reviewRounds.filter((round) => round.deltaRecheck === undefined);
12
7
  const deltaRounds = facts.reviewRounds.filter((round) => round.deltaRecheck !== undefined);
13
- const classifications = new Map(facts.reviewRounds.map((round) => [round.id, classifyReviewRoundOutcome(round, evidence)]));
14
- const semanticRounds = facts.reviewRounds.filter((round) => (classifications.get(round.id)?.kind === "semantic"));
15
- const p1P2Findings = facts.reviewFindings.filter((finding) => ((finding.severity === "p1" || finding.severity === "p2")
16
- && semanticRounds.some((round) => round.id === finding.firstReviewRoundId))).length;
17
8
  const candidateTimes = [
18
9
  ...facts.changeSets.map(({ createdAt }) => createdAt),
19
10
  ...facts.workItems.flatMap((item) => item.candidates
@@ -43,7 +34,7 @@ export function projectTaskOrchestration(facts) {
43
34
  reviewRounds: facts.reviewRounds,
44
35
  integrations: facts.integrations
45
36
  });
46
- const advisories = projectAdvisories(facts, classifications, fullRounds, repeatedIdentities, firstProgress.attentionRecommended);
37
+ const advisories = projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgress.attentionRecommended);
47
38
  const publicationAt = facts.task.completedAt === undefined
48
39
  ? undefined
49
40
  : facts.publications
@@ -66,12 +57,7 @@ export function projectTaskOrchestration(facts) {
66
57
  reviews: {
67
58
  full: fullRounds.length,
68
59
  delta: deltaRounds.length,
69
- nonSemantic: [...classifications.values()].filter((value) => value?.kind === "non-semantic").length,
70
- ambiguous: [...classifications.values()].filter((value) => value?.kind === "ambiguous").length,
71
- p1P2Findings,
72
- p1P2FindingsPerSemanticReview: semanticRounds.length === 0
73
- ? 0
74
- : p1P2Findings / semanticRounds.length
60
+ failed: facts.reviewRounds.filter(({ status }) => status === "failed").length
75
61
  },
76
62
  integrations: {
77
63
  attempts: facts.integrations.length,
@@ -87,7 +73,7 @@ export function projectTaskOrchestration(facts) {
87
73
  advisories
88
74
  });
89
75
  }
90
- function projectAdvisories(facts, classifications, fullRounds, repeatedIdentities, firstProgressAttention) {
76
+ function projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgressAttention) {
91
77
  const result = [];
92
78
  if (facts.task.type === "bugfix" && facts.workItems.length > 0) {
93
79
  result.push({
@@ -96,26 +82,6 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
96
82
  refs: facts.workItems.map(({ id }) => `work-item:${id}`)
97
83
  });
98
84
  }
99
- const repairItems = facts.workItems.filter((item) => (item.acceptance.some((line) => line.startsWith("review-finding:"))));
100
- const byRound = new Map();
101
- for (const item of repairItems) {
102
- const roundIds = new Set(item.acceptance.flatMap((line) => {
103
- const findingId = line.startsWith("review-finding:") ? line.slice("review-finding:".length) : "";
104
- const finding = facts.reviewFindings.find(({ id }) => id === findingId);
105
- return finding === undefined ? [] : [finding.firstReviewRoundId];
106
- }));
107
- for (const roundId of roundIds)
108
- byRound.set(roundId, [...(byRound.get(roundId) ?? []), item]);
109
- }
110
- for (const [roundId, items] of byRound) {
111
- if (items.length < 2 || hasRepairFanoutDecision(facts.decisions, roundId, items))
112
- continue;
113
- result.push({
114
- code: "review-repair-fanout",
115
- reason: `Findings from Review ${roundId} were split across ${items.length} WorkItems without a durable Decision explaining independent ownership.`,
116
- refs: [`review-round:${roundId}`, ...items.map(({ id }) => `work-item:${id}`)]
117
- });
118
- }
119
85
  if (repeatedIdentities > 0) {
120
86
  result.push({
121
87
  code: "repeated-integration-check",
@@ -125,19 +91,17 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
125
91
  .map(({ id }) => `durable-job:${id}`)
126
92
  });
127
93
  }
128
- const semanticFull = fullRounds.filter((round) => classifications.get(round.id)?.kind === "semantic")
94
+ const completedFull = fullRounds.filter((round) => (isCompletedReviewExecutionFromTurns(round, facts.turns)))
129
95
  .sort((left, right) => left.createdAt.localeCompare(right.createdAt));
130
- const recent = semanticFull.slice(-3);
131
- const recentIds = new Set(recent.map(({ id }) => id));
132
- const newFinding = facts.reviewFindings.some(({ firstReviewRoundId }) => recentIds.has(firstReviewRoundId));
96
+ const recent = completedFull.slice(-3);
133
97
  const sameCandidate = recent.length === 3
134
98
  && recent.every((round) => isDeepStrictEqual(round.taskCandidate, recent[0].taskCandidate));
135
99
  const sameReviewer = recent.length === 3
136
100
  && recent.every((round) => round.reviewerRoleName === recent[0].reviewerRoleName);
137
- if (sameCandidate && sameReviewer && !newFinding) {
101
+ if (sameCandidate && sameReviewer) {
138
102
  result.push({
139
103
  code: "repeated-full-review",
140
- reason: `The same Reviewer completed three full semantic Reviews of the same frozen candidate without a new finding; this is a cost advisory, not a Review limit.`,
104
+ reason: `The same Reviewer completed three full Reviews of the same frozen candidate; Core does not inspect their result text, so this is only a cost advisory.`,
141
105
  refs: recent.map(({ id }) => `review-round:${id}`)
142
106
  });
143
107
  }
@@ -150,12 +114,6 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
150
114
  }
151
115
  return result;
152
116
  }
153
- function hasRepairFanoutDecision(decisions, roundId, items) {
154
- return decisions.some((decision) => {
155
- const text = `${decision.title}\n${decision.rationale}`;
156
- return text.includes(roundId) && items.every(({ id }) => text.includes(id));
157
- });
158
- }
159
117
  function terminalWorkspaceCount(facts) {
160
118
  return facts.managedWorkspaces.filter(({ owner }) => {
161
119
  if (owner.type === "task")
@@ -13,7 +13,8 @@ import { spawnSync } from "node:child_process";
13
13
  import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  import Database from "better-sqlite3";
16
- import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "../storage/storageVersions.js";
16
+ import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "../storage/storageVersions.js";
17
+ import { inspectStorageSchema } from "../storage/storageSchema.js";
17
18
  import { resolveStoreWorkerEnabledForHome } from "../storage/storeRpc.js";
18
19
  export const UNSUPPORTED = "unsupported";
19
20
  /**
@@ -140,14 +141,7 @@ export function collectRuntimeBuildIdentity(ports) {
140
141
  export function createProductionStorageIdentityPorts(env = process.env) {
141
142
  return {
142
143
  env,
143
- readText: (path) => {
144
- try {
145
- return readFileSync(path, "utf8");
146
- }
147
- catch {
148
- return null;
149
- }
150
- },
144
+ inspectStorage: (home) => inspectStorageSchema(home),
151
145
  fileSize: (path) => {
152
146
  try {
153
147
  return statSync(path).size;
@@ -181,36 +175,12 @@ export function createProductionStorageIdentityPorts(env = process.env) {
181
175
  * are never treated as an alternate authority or a repair source.
182
176
  */
183
177
  export function collectStorageIdentity(home, ports = createProductionStorageIdentityPorts()) {
184
- const manifestPath = join(home, "schema.json");
185
- const manifestText = ports.readText(manifestPath);
186
- let manifestStatus = "uninitialized";
187
- let logicalLayout = UNSUPPORTED;
188
- let aggregateSchemaVersion = UNSUPPORTED;
189
- if (manifestText !== null) {
190
- manifestStatus = "current";
191
- try {
192
- const manifest = JSON.parse(manifestText);
193
- if (typeof manifest.storageVersion === "number"
194
- && Number.isFinite(manifest.storageVersion)) {
195
- logicalLayout = manifest.storageVersion;
196
- }
197
- if (typeof manifest.aggregateSchemaVersion === "number"
198
- && Number.isFinite(manifest.aggregateSchemaVersion)) {
199
- aggregateSchemaVersion = manifest.aggregateSchemaVersion;
200
- }
201
- if (typeof logicalLayout !== "number"
202
- || typeof aggregateSchemaVersion !== "number"
203
- || logicalLayout !== CURRENT_STORAGE_LAYOUT_VERSION
204
- || aggregateSchemaVersion !== CURRENT_AGGREGATE_SCHEMA_VERSION) {
205
- manifestStatus = "unsupported";
206
- }
207
- }
208
- catch {
209
- manifestStatus = "invalid";
210
- logicalLayout = UNSUPPORTED;
211
- aggregateSchemaVersion = UNSUPPORTED;
212
- }
213
- }
178
+ const storage = ports.inspectStorage(home);
179
+ const storageVersion = storage.status === "current"
180
+ || storage.status === "upgradeable"
181
+ || storage.status === "unsupported"
182
+ ? storage.currentVersion
183
+ : UNSUPPORTED;
214
184
  const statePath = join(home, "state.json");
215
185
  const statePresent = ports.exists(statePath);
216
186
  const stateBytes = ports.fileSize(statePath);
@@ -223,7 +193,7 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
223
193
  const workerEnabled = resolveStoreWorkerEnabledForHome(home, ports.env);
224
194
  const dbHealth = dbPresent ? ports.probeDatabaseHealth(dbPath) : null;
225
195
  const findings = [];
226
- if (manifestStatus === "current" && !dbPresent) {
196
+ if (storage.status === "current" && !dbPresent) {
227
197
  findings.push({
228
198
  code: "current-database-missing",
229
199
  severity: "contradiction",
@@ -231,20 +201,48 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
231
201
  remediation: "Preserve this Home for diagnosis and initialize a new Home."
232
202
  });
233
203
  }
234
- if (manifestStatus === "unsupported") {
204
+ if (storage.status === "uninitialized") {
205
+ findings.push({
206
+ code: "storage-uninitialized",
207
+ severity: "needs-repair",
208
+ message: "Yui storage has not been initialized for this Home.",
209
+ remediation: "Run `yui setup` for a new, empty Home."
210
+ });
211
+ }
212
+ if (storage.status === "upgradeable") {
213
+ findings.push({
214
+ code: "storage-upgrade-required",
215
+ severity: "needs-repair",
216
+ message: `Storage version ${storage.currentVersion} requires migration to `
217
+ + `${CURRENT_STORAGE_VERSION}.`,
218
+ remediation: "Run `yui upgrade` or `yui update` before resuming writes."
219
+ });
220
+ }
221
+ if (storage.status === "unsupported") {
235
222
  findings.push({
236
223
  code: "unsupported-storage-contract",
237
224
  severity: "contradiction",
238
- message: "The Home does not match this release's exact storage contract.",
239
- remediation: "Open it read-only with its original Yui version, then let the Operator recreate unfinished work in a new Home."
225
+ message: `Storage version ${storage.currentVersion} is outside the supported migration range `
226
+ + `${MIN_SUPPORTED_STORAGE_VERSION}..${CURRENT_STORAGE_VERSION}.`,
227
+ remediation: storage.direction === "newer"
228
+ ? "Use a newer Yui release."
229
+ : "Use a Yui release whose migration floor includes this Home."
240
230
  });
241
231
  }
242
- if (manifestStatus === "invalid") {
232
+ if (storage.status === "invalid") {
243
233
  findings.push({
244
- code: "invalid-storage-manifest",
234
+ code: "invalid-storage",
245
235
  severity: "contradiction",
246
- message: "schema.json is invalid.",
247
- remediation: "Preserve this Home for diagnosis and initialize a new Home."
236
+ message: storage.detail,
237
+ remediation: "Preserve this Home for diagnosis and restore a known-good backup."
238
+ });
239
+ }
240
+ if (ports.exists(join(home, "schema.json"))) {
241
+ findings.push({
242
+ code: "ignored-legacy-storage-manifest",
243
+ severity: "warning",
244
+ message: "schema.json is legacy metadata and is not a storage authority.",
245
+ remediation: "A successful storage upgrade removes it automatically."
248
246
  });
249
247
  }
250
248
  if (dbPresent && dbHealth !== null && dbHealth !== "ok") {
@@ -274,9 +272,9 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
274
272
  }
275
273
  return {
276
274
  home,
277
- manifestStatus,
278
- logicalLayout,
279
- aggregateSchemaVersion,
275
+ storageStatus: storage.status,
276
+ storageVersion,
277
+ minimumStorageVersion: MIN_SUPPORTED_STORAGE_VERSION,
280
278
  configuredBackend,
281
279
  workerEnabled,
282
280
  physicalStateJson: {
@@ -8,7 +8,7 @@ export const BUILTIN_PROFILE_IDS = Object.freeze([
8
8
  export function createAgentProfile(input, now) {
9
9
  const timestamp = now.toISOString();
10
10
  return validateAgentProfile({
11
- schemaVersion: 2,
11
+ schemaVersion: 3,
12
12
  id: requireIdentity(input.id, "Agent Profile id"),
13
13
  revision: 1,
14
14
  ...normalizeInput(input),
@@ -24,15 +24,15 @@ export function updateAgentProfile(profile, patch, now) {
24
24
  revision: profile.revision + 1,
25
25
  updatedAt: now.toISOString()
26
26
  };
27
- for (const key of ["description", "instructions", "skills", "model", "effort"]) {
27
+ for (const key of ["description", "instructions", "skills"]) {
28
28
  if (candidate[key] === undefined)
29
29
  delete candidate[key];
30
30
  }
31
31
  return validateAgentProfile(candidate);
32
32
  }
33
33
  export function validateAgentProfile(profile) {
34
- if (profile.schemaVersion !== 2) {
35
- throw new Error("AgentProfile must use schemaVersion 2.");
34
+ if (profile.schemaVersion !== 3) {
35
+ throw new Error("AgentProfile must use schemaVersion 3.");
36
36
  }
37
37
  requireIdentity(profile.id, "Agent Profile id");
38
38
  requirePositiveInteger(profile.revision, "Agent Profile revision");
@@ -43,10 +43,7 @@ export function validateAgentProfile(profile) {
43
43
  optionalText(profile.instructions, "Profile instructions");
44
44
  if (profile.skills !== undefined)
45
45
  normalizedUniqueText(profile.skills, "Profile skill");
46
- if (profile.model !== undefined)
47
- optionalText(profile.model, "Profile model");
48
- if (profile.effort !== undefined)
49
- optionalText(profile.effort, "Profile effort");
46
+ validateAgentProfileRuntime(profile.runtime);
50
47
  requireTimestamp(profile.createdAt, "Agent Profile createdAt");
51
48
  requireTimestamp(profile.updatedAt, "Agent Profile updatedAt");
52
49
  if (Date.parse(profile.updatedAt) < Date.parse(profile.createdAt)) {
@@ -59,24 +56,28 @@ export function builtinAgentProfileInputs() {
59
56
  {
60
57
  id: "worker",
61
58
  description: "Implement and validate one bounded delegated WorkItem.",
62
- defaultAccess: "write"
59
+ defaultAccess: "write",
60
+ runtime: { source: "global-worker" }
63
61
  },
64
62
  {
65
63
  id: "explorer",
66
64
  description: "Inspect sources and return evidence without modifying them.",
67
65
  instructions: "Do not modify files or external state.",
68
- defaultAccess: "read"
66
+ defaultAccess: "read",
67
+ runtime: { source: "global-worker" }
69
68
  },
70
69
  {
71
70
  id: "implementer",
72
71
  description: "Implement and validate one bounded result.",
73
- defaultAccess: "write"
72
+ defaultAccess: "write",
73
+ runtime: { source: "global-worker" }
74
74
  },
75
75
  {
76
76
  id: "reviewer",
77
77
  description: "Review one candidate against the user's core outcome, supported behavior, and direct evidence.",
78
78
  instructions: "Start from user intent and acceptance criteria. Inspect the complete relevant change and report only reachable, material, actionable problems with direct evidence. Separate defects from verification gaps, and prefer the smallest sufficient correction. Follow the bound Project's Policy and Knowledge for build, test, migration, release, and review expectations; do not import rules from another Project or Task. For normal software delivery, review the frozen Task result as one final ReviewRound rather than inventing a per-WorkItem protocol unless the Project Policy explicitly requires one. A Task-final Round has no synthetic WorkItem anchor, and the Reviewer Session and physical workspace continue across changed-head Rounds without reusing an earlier verdict. The current Turn's frozen candidate remains the only scope even if Task main advances. For Delta Recheck, return equivalent-and-accepted, finding, or requires-full-review with explicit reasoning; never create the next Round. Do not turn speculative or extreme edge cases into new state, retries, fallbacks, or protocol. In the Review workspace you may edit source or tests, run local checks, and optionally commit diagnostic evidence. Never push, integrate, mutate Task state, touch another workspace or stable checkout, or write the real Yui control-plane home. End the Provider turn with complete findings, checks actually run, uncertainty, and bounded next actions; Yui preserves the full free-form report automatically. Expose evidence and options to the Leader, who decides.",
79
- defaultAccess: "write"
79
+ defaultAccess: "write",
80
+ runtime: { source: "global-worker" }
80
81
  }
81
82
  ];
82
83
  }
@@ -92,8 +93,7 @@ function normalizeInput(input) {
92
93
  ...(input.skills === undefined
93
94
  ? {}
94
95
  : { skills: normalizedUniqueText(input.skills, "Profile skill") }),
95
- ...(input.model === undefined ? {} : { model: optionalText(input.model, "Profile model") }),
96
- ...(input.effort === undefined ? {} : { effort: optionalText(input.effort, "Profile effort") })
96
+ runtime: normalizeRuntime(input.runtime ?? { source: "global-worker" })
97
97
  };
98
98
  }
99
99
  function normalizePatch(patch) {
@@ -101,7 +101,7 @@ function normalizePatch(patch) {
101
101
  if (Object.hasOwn(patch, "defaultAccess")) {
102
102
  result.defaultAccess = validatedAccess(patch.defaultAccess);
103
103
  }
104
- for (const key of ["description", "instructions", "model", "effort"]) {
104
+ for (const key of ["description", "instructions"]) {
105
105
  if (!Object.hasOwn(patch, key))
106
106
  continue;
107
107
  result[key] = patch[key] === undefined
@@ -113,8 +113,53 @@ function normalizePatch(patch) {
113
113
  ? undefined
114
114
  : normalizedUniqueText(patch.skills, "Profile skill");
115
115
  }
116
+ if (Object.hasOwn(patch, "runtime")) {
117
+ if (patch.runtime === undefined) {
118
+ throw new Error("Agent Profile runtime is required.");
119
+ }
120
+ result.runtime = normalizeRuntime(patch.runtime);
121
+ }
116
122
  return result;
117
123
  }
124
+ function normalizeRuntime(runtime) {
125
+ validateAgentProfileRuntime(runtime);
126
+ return runtime.source === "global-worker"
127
+ ? { source: "global-worker" }
128
+ : {
129
+ source: "explicit",
130
+ agentId: requireIdentity(runtime.agentId, "Profile Agent id"),
131
+ ...(runtime.model === undefined
132
+ ? {}
133
+ : { model: optionalText(runtime.model, "Profile model") }),
134
+ ...(runtime.effort === undefined
135
+ ? {}
136
+ : { effort: optionalText(runtime.effort, "Profile effort") })
137
+ };
138
+ }
139
+ function validateAgentProfileRuntime(runtime) {
140
+ if (runtime === null || typeof runtime !== "object" || Array.isArray(runtime)) {
141
+ throw new Error("Agent Profile runtime is invalid.");
142
+ }
143
+ const source = runtime.source;
144
+ if (source === "global-worker") {
145
+ if (Object.keys(runtime).some((key) => key !== "source")) {
146
+ throw new Error("Worker-inherited Agent Profile runtime cannot persist Agent settings.");
147
+ }
148
+ return;
149
+ }
150
+ if (source !== "explicit") {
151
+ throw new Error(`Agent Profile runtime source is invalid: ${String(source)}.`);
152
+ }
153
+ if (Object.keys(runtime).some((key) => (key !== "source" && key !== "agentId" && key !== "model" && key !== "effort"))) {
154
+ throw new Error("Explicit Agent Profile runtime contains unsupported fields.");
155
+ }
156
+ const explicit = runtime;
157
+ requireIdentity(explicit.agentId, "Profile Agent id");
158
+ if (explicit.model !== undefined)
159
+ optionalText(explicit.model, "Profile model");
160
+ if (explicit.effort !== undefined)
161
+ optionalText(explicit.effort, "Profile effort");
162
+ }
118
163
  function validatedAccess(value) {
119
164
  if (value === undefined)
120
165
  throw new Error("Agent Profile default access is required.");