@zq-silk/yui 0.11.3 → 0.12.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 (86) hide show
  1. package/ARCHITECTURE.md +23 -4
  2. package/README.md +36 -13
  3. package/dist/cli/commandCatalog.js +2 -4
  4. package/dist/cli.js +105 -43
  5. package/dist/commands/configCommands.js +12 -45
  6. package/dist/commands/executionAuditCommands.js +1 -1
  7. package/dist/commands/jobCommands.js +4 -10
  8. package/dist/commands/taskCommands.js +154 -171
  9. package/dist/commands/taskCompletionGate.js +29 -54
  10. package/dist/commands/taskContextCommand.js +55 -11
  11. package/dist/commands/taskInputCommands.js +8 -5
  12. package/dist/commands/taskNextActionCommand.js +56 -6
  13. package/dist/commands/taskOverviewCommand.js +17 -31
  14. package/dist/commands/taskRoleRuntimeStatus.js +24 -0
  15. package/dist/commands/taskWorkspaceCommands.js +5 -2
  16. package/dist/config/configCatalog.js +2 -2
  17. package/dist/config/yuiConfig.js +3 -7
  18. package/dist/context/wakeNotification.js +20 -5
  19. package/dist/controller/agentRuntimeObserver.js +247 -51
  20. package/dist/controller/clientRuntime.js +1 -1
  21. package/dist/controller/controller.js +8 -12
  22. package/dist/controller/fileSchedulerStoreAdapter.js +146 -198
  23. package/dist/controller/runtime.js +3 -3
  24. package/dist/controller/runtimeEventInbox.js +17 -7
  25. package/dist/controller/runtimeEventProcessor.js +12 -19
  26. package/dist/controller/runtimeObservationHook.js +45 -0
  27. package/dist/execution/resourceBroker.js +5 -4
  28. package/dist/executor/agentExecutor.js +4 -4
  29. package/dist/executor/effectiveLaunch.js +8 -48
  30. package/dist/executor/fileRoleLaunchPlanner.js +36 -16
  31. package/dist/executor/workspacePreflightClassification.js +23 -2
  32. package/dist/interaction/operatorPresentation.js +33 -89
  33. package/dist/lifecycle/exactRunTerminalization.js +1 -2
  34. package/dist/lifecycle/taskRoleSessionReset.js +5 -13
  35. package/dist/observability/orchestrationMetrics.js +8 -3
  36. package/dist/profile/agentProfile.js +1 -1
  37. package/dist/release/cliHomeReleaseFence.js +123 -0
  38. package/dist/repository/taskWorkspacePreparer.js +176 -60
  39. package/dist/resources/sqliteResourceRegistry.js +1 -1
  40. package/dist/review/deltaRecheck.js +12 -51
  41. package/dist/review/reviewAcceptance.js +26 -0
  42. package/dist/review/reviewConfig.js +0 -31
  43. package/dist/review/reviewDecision.js +113 -0
  44. package/dist/review/reviewOutcomeClassifier.js +1 -1
  45. package/dist/review/reviewRound.js +1 -1
  46. package/dist/review/reviewerAvailability.js +69 -0
  47. package/dist/runtime/agentDriverObservation.js +24 -10
  48. package/dist/runtime/builtinAgentDrivers.js +4 -3
  49. package/dist/runtime/builtinTranscriptObserver.js +301 -64
  50. package/dist/runtime/builtinTranscriptUsage.js +9 -7
  51. package/dist/runtime/index.js +1 -0
  52. package/dist/runtime/runtimeObservation.js +13 -0
  53. package/dist/runtime/runtimeProjection.js +16 -25
  54. package/dist/runtime/sessionTokenMetrics.js +181 -0
  55. package/dist/runtime/structuredProviderHost.js +7 -1
  56. package/dist/scheduler/activeRoleRunDelivery.js +4 -4
  57. package/dist/scheduler/activeTaskProgress.js +7 -6
  58. package/dist/scheduler/leaderWakeupProcessor.js +3 -5
  59. package/dist/scheduler/operatorEvent.js +34 -0
  60. package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
  61. package/dist/scheduler/roleRunStall.js +0 -14
  62. package/dist/scheduler/taskExecutionProjection.js +97 -76
  63. package/dist/scheduler/taskObservabilityProjection.js +10 -11
  64. package/dist/storage/migration/productionRegistry.js +379 -0
  65. package/dist/storage/sqliteSchema.js +18 -8
  66. package/dist/storage/sqliteStore.js +24 -20
  67. package/dist/storage/storeRpc.js +0 -1
  68. package/dist/storage/taskStore.js +15 -30
  69. package/dist/storage/upgrade/recordVersions.js +3 -2
  70. package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
  71. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
  72. package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
  73. package/dist/task/completionReadiness.js +10 -6
  74. package/dist/task/nextAction.js +82 -24
  75. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  76. package/dist/web/assets/client/components.js +13 -3
  77. package/dist/web/assets/client/i18n.js +6 -2
  78. package/dist/web/webSnapshot.js +4 -0
  79. package/i18n/README.zh-CN.md +2 -2
  80. package/package.json +1 -1
  81. package/skills/yui-leader/SKILL.md +53 -16
  82. package/skills/yui-operator/SKILL.md +9 -0
  83. package/skills/yui-reviewer/SKILL.md +28 -7
  84. package/dist/context/sessionContextBudget.js +0 -71
  85. package/dist/lifecycle/contextBudgetRollover.js +0 -81
  86. package/dist/scheduler/operatorNotification.js +0 -59
@@ -1,96 +1,40 @@
1
- import { formatTimestamp } from "../output/timePresentation.js";
2
- import { formatInputRequestReceiptId, formatTaskRecordReference } from "../task/taskRecordReference.js";
3
- export function createInputRequestOperatorPresentation(request, context) {
4
- const policy = request.policy;
5
- const recommendation = policy.kind === "recommended"
6
- ? request.choices.find(({ key }) => key === policy.recommendedChoiceKey)
7
- : undefined;
8
- if (policy.kind === "recommended" && recommendation === undefined) {
9
- throw new Error(`Input request ${request.id} recommendation ${policy.recommendedChoiceKey} is missing.`);
10
- }
11
- return {
12
- category: "attention",
13
- taskId: request.taskId,
14
- receiptId: formatInputRequestReceiptId(request.taskId, request.id),
15
- source: { kind: "input-request", taskId: request.taskId, localId: request.id },
16
- text: [
17
- "A Task Leader is waiting for a boundary decision. Inspect it before involving the user.",
18
- "Present it only when it is a user-owned choice, authorization, credential, unavailable external fact, or irreversible operation.",
19
- `If it is an implementation, scheduling, review, or recoverable runtime choice, do not present it to the user; return it to the Leader with: yui task input cancel ${request.taskId} ${request.id} --reason \"Operator returned: not a user-owned boundary\"`,
20
- "For a user-owned boundary, do not answer it yourself; do not answer or choose on the user's behalf; relay the user's exact response only.",
21
- `Task: ${request.taskId}`,
22
- `Input: ${request.id}`,
23
- `Question: ${request.question}`,
24
- ...(request.choices.length === 0
25
- ? ["Answer type: free text"]
26
- : ["Choices:", ...request.choices.map(({ key, label }) => ` ${key}: ${label}`)]),
27
- ...(policy.kind === "required"
28
- ? ["Decision policy: this requires the user's response; there is no automatic fallback."]
29
- : [
30
- `Agent recommendation: ${recommendation.key}: ${recommendation.label}`,
31
- `Automatic fallback after: ${formatTimestamp(policy.timeoutAt, context.timeZone)}`
32
- ]),
33
- `Inspect: yui task input show ${formatTaskRecordReference(request.taskId, request.id, "inputRequest")}`,
34
- request.choices.length === 0
35
- ? `After the user replies: yui task input answer ${formatTaskRecordReference(request.taskId, request.id, "inputRequest")} --text "<answer>"`
36
- : `After the user chooses: yui task input answer ${formatTaskRecordReference(request.taskId, request.id, "inputRequest")} --choice <key>`
37
- ].join("\n")
38
- };
39
- }
40
- export function createLeaderRecoveryOperatorPresentation(notification) {
1
+ /**
2
+ * One mailbox batch becomes one short synthetic user message. The durable
3
+ * records remain the context authority; this wake text carries only stable
4
+ * identities and exact CLI reads, never copied Task or Provider narrative.
5
+ */
6
+ export function createOperatorBatchPresentation(batchId, items) {
7
+ if (items.length === 0)
8
+ throw new Error("Operator presentation requires at least one item.");
41
9
  return {
42
- category: "attention",
43
- taskId: notification.taskId,
44
- receiptId: `leader-recovery:${notification.taskId}:${notification.createdAt}`,
45
- source: { kind: "leader-recovery", id: notification.taskId },
10
+ receiptId: `operator-batch:${batchId}`,
46
11
  text: [
47
- "A Task cannot recover its Leader automatically and needs user attention.",
48
- `Task: ${notification.taskId}`,
49
- `Failure: ${notification.message}`,
50
- `Inspect: yui task show ${notification.taskId}`,
51
- "Recovery status: yui jobs list",
52
- `Retry after inspection: yui jobs retry leader-recovery:${notification.taskId}`
12
+ "[Yui updates]",
13
+ "Yui recorded the following durable updates. Inspect the referenced records and present only information that changes the user's understanding, authorization, or next action.",
14
+ ...items.flatMap(renderItem)
53
15
  ].join("\n")
54
16
  };
55
17
  }
56
- export function createLeaderStallOperatorPresentation(notification) {
57
- return {
58
- category: "attention",
59
- taskId: notification.taskId,
60
- receiptId: `leader-stall:${notification.taskId}:${notification.runId}:${notification.progressAt}`,
61
- source: {
62
- kind: "leader-stall",
63
- id: `${notification.runId}:${notification.progressAt}`
64
- },
65
- text: [
66
- "A Task Leader is truly stalled and needs user attention.",
67
- `Task: ${notification.taskId}`,
68
- `Leader Run: ${notification.runId}`,
69
- `lastProgressAt: ${notification.progressAt}`,
70
- "Classification: truly-stalled",
71
- `Evidence: ${notification.evidenceKey}`,
72
- "Controller preserved the exact Run/Session fence and performed no automatic Enter, reset, retry, kill, or Session replacement.",
73
- `Inspect: yui task context ${notification.taskId}`,
74
- "Choose the supported continuation, reset, retry, Agent change, or user-input action only after inspection."
75
- ].join("\n")
76
- };
18
+ function renderItem(item) {
19
+ if (item.kind === "input-request") {
20
+ const { request } = item;
21
+ return [
22
+ `- User input requested: ${request.taskId}/${request.id}`,
23
+ ` Inspect: yui task input show ${request.taskId}/${request.id}`
24
+ ];
25
+ }
26
+ const { event } = item;
27
+ return [
28
+ `- ${eventLabel(event)}: ${event.taskId}/${event.id}`,
29
+ ` Inspect: yui task event show ${event.taskId} ${event.id}`
30
+ ];
77
31
  }
78
- export function createTaskTerminalOperatorPresentation(notification) {
79
- const action = notification.status === "completed" ? "completed" : "retired";
80
- const actor = notification.by === "leader"
81
- ? "its Leader"
82
- : notification.by === "operator"
83
- ? "the Operator"
84
- : "the user";
85
- return {
86
- category: "information",
87
- taskId: notification.taskId,
88
- receiptId: `task-terminal:${notification.taskId}:${notification.status}:${notification.createdAt}`,
89
- source: { kind: "task-terminal", id: notification.taskId },
90
- text: [
91
- `Task ${notification.taskId} was ${action} by ${actor}.`,
92
- `Summary: ${notification.summary}`,
93
- `Inspect: yui task show ${notification.taskId}`
94
- ].join("\n")
95
- };
32
+ function eventLabel(event) {
33
+ switch (event.type) {
34
+ case "task.completed": return "Task completed";
35
+ case "task.retired": return "Task retired";
36
+ case "leader.attention-required": return "Task Leader needs attention";
37
+ case "run.stalled": return "Task Leader stalled";
38
+ default: return `Task event ${event.type}`;
39
+ }
96
40
  }
@@ -12,7 +12,7 @@ import { createYieldReceipt } from "../run/yieldReceipt.js";
12
12
  import { recordExecutionLaneResult } from "../execution/executionGroup.js";
13
13
  import { isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
14
14
  import { runOwnsBlockingProviderContinuation } from "../runtime/runtimeContinuationProjection.js";
15
- import { clearMatchingLeaderStallAttention, latestRunDurableProgressAt, RUN_RECOVERY_APPLIED_EVENT, RUN_RECOVERY_REQUESTED_EVENT } from "../scheduler/roleRunStall.js";
15
+ import { latestRunDurableProgressAt, RUN_RECOVERY_APPLIED_EVENT, RUN_RECOVERY_REQUESTED_EVENT } from "../scheduler/roleRunStall.js";
16
16
  import { markTaskWakeConsumed } from "../scheduler/taskWake.js";
17
17
  import { workItemExecutionGroupById, workItemOwnsUnresolvedExecutionLane, updateWorkItemExecutionGroup, updateWorkItemStatus } from "../workItem/workItem.js";
18
18
  /**
@@ -496,7 +496,6 @@ function recoverExactAgentRunInTransaction(store, input) {
496
496
  }
497
497
  }
498
498
  store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, RUN_RECOVERY_APPLIED_EVENT, { ...eventPayload, status: "terminated" }, input.now));
499
- clearMatchingLeaderStallAttention(store, input.taskId, input.runId);
500
499
  if (input.roleName !== "leader") {
501
500
  enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, "run-recovery-terminated", input.now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
502
501
  }
@@ -3,7 +3,7 @@ import { resetTaskRoleSession } from "../executor/agentExecutor.js";
3
3
  import { createTaskEvent } from "../event/taskEvent.js";
4
4
  import { createTaskMessage } from "../message/message.js";
5
5
  import { updateRoleStatus } from "../role/role.js";
6
- import { createLeaderRecoveryNotification } from "../scheduler/operatorNotification.js";
6
+ import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
7
7
  import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
8
8
  import { RUNTIME_CLEANUP_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
9
9
  import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
@@ -70,27 +70,19 @@ export function resetTaskRoleSessionGeneration(store, taskId, roleName, reason,
70
70
  ...(activeRun.workItemId === undefined ? {} : { workItemId: activeRun.workItemId })
71
71
  });
72
72
  store.saveMessage(task.id, message);
73
- store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "runtime.role-session-reset", {
73
+ const resetEvent = createTaskEvent(store.nextEventId(task.id), task.id, "runtime.role-session-reset", {
74
74
  roleName: role.name,
75
75
  reason: summary,
76
76
  ...(activeRun === null ? {} : { runId: activeRun.id }),
77
77
  ...(current?.nativeSessionId === undefined
78
78
  ? {}
79
79
  : { nativeSessionId: current.nativeSessionId })
80
- }, now));
80
+ }, now);
81
+ store.saveEvent(task.id, resetEvent);
81
82
  if (role.name === "leader") {
82
83
  const nativeSessionId = current?.nativeSessionId ?? `reset-${task.id}`;
83
84
  store.saveLeaderFailure(recordLeaderFailure(task.id, nativeSessionId, summary, now, store.getLeaderFailure(task.id)));
84
- store.saveOperatorNotification(createLeaderRecoveryNotification(task.id, summary, now, store.getOperatorNotification(task.id)));
85
- enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
86
- { type: "task", id: task.id },
87
- { type: "message", taskId: task.id, id: message.id },
88
- ...(activeRun === null ? [] : [{
89
- type: "run",
90
- taskId: task.id,
91
- id: activeRun.id
92
- }])
93
- ]);
85
+ enqueueOperatorEvent(store, resetEvent, "leader-run-failed", now);
94
86
  }
95
87
  else {
96
88
  enqueueWork(store, { kind: "role", taskId: task.id, roleName: "leader" }, "role-run-failed", now, [
@@ -1,3 +1,4 @@
1
+ import { isDeepStrictEqual } from "node:util";
1
2
  import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
2
3
  import { projectFirstProgressAdvisory } from "../runtime/firstProgressAdvisory.js";
3
4
  /** One Task's orchestration cost and advisory projection, with no writes. */
@@ -129,10 +130,14 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
129
130
  const recent = semanticFull.slice(-3);
130
131
  const recentIds = new Set(recent.map(({ id }) => id));
131
132
  const newFinding = facts.reviewFindings.some(({ firstReviewRoundId }) => recentIds.has(firstReviewRoundId));
132
- if (semanticFull.length > 2 && !newFinding) {
133
+ const sameCandidate = recent.length === 3
134
+ && recent.every((round) => isDeepStrictEqual(round.taskCandidate, recent[0].taskCandidate));
135
+ const sameReviewer = recent.length === 3
136
+ && recent.every((round) => round.reviewerRoleName === recent[0].reviewerRoleName);
137
+ if (sameCandidate && sameReviewer && !newFinding) {
133
138
  result.push({
134
- code: "review-budget-exhausted",
135
- reason: `${semanticFull.length} full semantic Reviews ran and the latest three produced no new finding; stop repeating full rounds without a changed head or new risk.`,
139
+ 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.`,
136
141
  refs: recent.map(({ id }) => `review-round:${id}`)
137
142
  });
138
143
  }
@@ -75,7 +75,7 @@ export function builtinAgentProfileInputs() {
75
75
  {
76
76
  id: "reviewer",
77
77
  description: "Review one candidate against the user's core outcome, supported behavior, and direct evidence.",
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 a compatible Reviewer Session may continue across changed-head Rounds without reusing an earlier verdict. Do not turn speculative or extreme edge cases into new state, retries, fallbacks, or protocol. In a ReviewRound-owned 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. Report complete findings, checks actually run, uncertainty, and bounded next actions through the exact Review yield; Yui preserves the full free-form report. Expose evidence and options to the Leader, who decides.",
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 Run'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. Report complete findings, checks actually run, uncertainty, and bounded next actions through the exact Review yield; Yui preserves the full free-form report. Expose evidence and options to the Leader, who decides.",
79
79
  defaultAccess: "write"
80
80
  }
81
81
  ];
@@ -0,0 +1,123 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { UNSUPPORTED, collectRuntimeBuildIdentity, createProductionRuntimeIdentityPorts } from "../observability/runtimeIdentity.js";
4
+ import { readCompatibleHomeIdentity } from "../storage/compatibleTaskStore.js";
5
+ import { detectRunningRelease, readActiveReleasePointer, releaseDirectoryName } from "./runtimeRelease.js";
6
+ /**
7
+ * Prevent a checkout or otherwise unverified CLI from entering a Home that is
8
+ * already owned by an immutable active release. This fence runs before any
9
+ * TaskStore is opened, so even schema discovery cannot advance SQLite state.
10
+ * Published global packages remain the stable ordinary CLI boundary; an exact
11
+ * installed release may also run when it is active or is the explicit handover
12
+ * target.
13
+ */
14
+ export function assertCliHomeReleaseFence(input) {
15
+ const active = readActiveReleasePointer(input.home);
16
+ if (active === null)
17
+ return "unregistered-home";
18
+ const running = detectRunningRelease(input.entryPath);
19
+ if (running !== null) {
20
+ if (matchesActiveRelease(running.manifest, active))
21
+ return "active-release";
22
+ if (isExplicitActivation(input.args, running.manifest))
23
+ return "release-activation";
24
+ throw fenceError(input, active, "installed-release", running.manifest);
25
+ }
26
+ const build = collectRuntimeBuildIdentity(createProductionRuntimeIdentityPorts(input.packageRoot, input.entryPath));
27
+ const distribution = readPackageDistribution(input.packageRoot);
28
+ if (distribution === "published" && build.sourceCommit === UNSUPPORTED) {
29
+ return "published-package";
30
+ }
31
+ const source = distribution === "checkout" || build.sourceCommit !== UNSUPPORTED
32
+ ? "checkout-local"
33
+ : "unverified-local";
34
+ throw fenceError(input, active, source, undefined, build);
35
+ }
36
+ /** Render bounded, non-secret identity evidence for a migration diagnosis. */
37
+ export function describeCliHomeInvocation(input) {
38
+ const running = detectRunningRelease(input.entryPath);
39
+ const collected = collectRuntimeBuildIdentity(createProductionRuntimeIdentityPorts(input.packageRoot, input.entryPath));
40
+ const distribution = readPackageDistribution(input.packageRoot);
41
+ const source = running !== null
42
+ ? "installed-release"
43
+ : distribution === "published" && collected.sourceCommit === UNSUPPORTED
44
+ ? "published-package"
45
+ : distribution === "checkout" || collected.sourceCommit !== UNSUPPORTED
46
+ ? "checkout-local"
47
+ : "unverified-local";
48
+ let homeId = "unknown";
49
+ try {
50
+ homeId = readCompatibleHomeIdentity(input.home).homeId;
51
+ }
52
+ catch {
53
+ // The primary migration error remains authoritative when identity is unreadable.
54
+ }
55
+ let activeRelease = "none";
56
+ let activeBuild = "none";
57
+ try {
58
+ const active = readActiveReleasePointer(input.home);
59
+ if (active !== null) {
60
+ activeRelease = active.releaseId;
61
+ activeBuild = active.buildId;
62
+ }
63
+ }
64
+ catch {
65
+ activeRelease = "unreadable";
66
+ activeBuild = "unreadable";
67
+ }
68
+ const version = running?.manifest.version ?? collected.packageVersion;
69
+ const buildId = running?.manifest.buildId
70
+ ?? (source === "checkout-local" ? "checkout" : source);
71
+ const sourceCommit = running?.manifest.sourceCommit ?? (collected.sourceCommit === UNSUPPORTED ? "unknown" : collected.sourceCommit);
72
+ return [
73
+ `CLI: version=${diagnostic(version)} build=${diagnostic(buildId)} sourceCommit=${diagnostic(sourceCommit)}`,
74
+ `Home: homeId=${diagnostic(homeId)} activeRelease=${diagnostic(activeRelease)} activeBuild=${diagnostic(activeBuild)}`,
75
+ `Invocation: source=${source}`
76
+ ].join("\n");
77
+ }
78
+ function matchesActiveRelease(manifest, active) {
79
+ return active.releaseId === releaseDirectoryName(manifest)
80
+ && active.version === manifest.version
81
+ && active.buildId === manifest.buildId
82
+ && active.packageDigest === manifest.packageDigest;
83
+ }
84
+ function isExplicitActivation(args, manifest) {
85
+ if (args.length !== 3 || args[0] !== "release" || args[1] !== "activate")
86
+ return false;
87
+ return args[2] === releaseDirectoryName(manifest) || args[2] === manifest.buildId;
88
+ }
89
+ function readPackageDistribution(packageRoot) {
90
+ try {
91
+ const value = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
92
+ if (value.private === true)
93
+ return "checkout";
94
+ if (value.private === false)
95
+ return "published";
96
+ }
97
+ catch {
98
+ // A missing or damaged package identity is not publication evidence.
99
+ }
100
+ return "unknown";
101
+ }
102
+ function fenceError(input, active, source, release, collected = collectRuntimeBuildIdentity(createProductionRuntimeIdentityPorts(input.packageRoot, input.entryPath))) {
103
+ let homeId = "unknown";
104
+ try {
105
+ homeId = readCompatibleHomeIdentity(input.home).homeId;
106
+ }
107
+ catch {
108
+ // The release fence must still diagnose and reject an unreadable Home.
109
+ }
110
+ const version = release?.version ?? collected.packageVersion;
111
+ const buildId = release?.buildId ?? (source === "checkout-local" ? "checkout" : "unknown");
112
+ const sourceCommit = release?.sourceCommit ?? (collected.sourceCommit === UNSUPPORTED ? "unknown" : collected.sourceCommit);
113
+ return new Error([
114
+ `Refusing ${source} CLI access to a Home owned by another active release.`,
115
+ `CLI: version=${diagnostic(version)} build=${diagnostic(buildId)} sourceCommit=${diagnostic(sourceCommit)}`,
116
+ `Home: homeId=${diagnostic(homeId)} activeRelease=${diagnostic(active.releaseId)} activeBuild=${diagnostic(active.buildId)}`,
117
+ `Invocation: source=${source}`,
118
+ "Use the published global yui for this Home, or run make install-local without overriding its isolated YUI_HOME."
119
+ ].join("\n"));
120
+ }
121
+ function diagnostic(value) {
122
+ return value.replace(/[^A-Za-z0-9._:+/@-]/gu, "?").slice(0, 160);
123
+ }