@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.
- package/ARCHITECTURE.md +23 -4
- package/README.md +36 -13
- package/dist/cli/commandCatalog.js +2 -4
- package/dist/cli.js +105 -43
- package/dist/commands/configCommands.js +12 -45
- package/dist/commands/executionAuditCommands.js +1 -1
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +154 -171
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +55 -11
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +24 -0
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +2 -2
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +1 -1
- package/dist/controller/controller.js +8 -12
- package/dist/controller/fileSchedulerStoreAdapter.js +146 -198
- package/dist/controller/runtime.js +3 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +4 -4
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +36 -16
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +1 -2
- package/dist/lifecycle/taskRoleSessionReset.js +5 -13
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/runtimeObservation.js +13 -0
- package/dist/runtime/runtimeProjection.js +16 -25
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/scheduler/activeRoleRunDelivery.js +4 -4
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +3 -5
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +0 -14
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +18 -8
- package/dist/storage/sqliteStore.js +24 -20
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -24
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +4 -0
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +53 -16
- package/skills/yui-operator/SKILL.md +9 -0
- package/skills/yui-reviewer/SKILL.md +28 -7
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -1,96 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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: "
|
|
135
|
-
reason:
|
|
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
|
|
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
|
+
}
|