@zq-silk/yui 2.8.0 → 2.8.1

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.
@@ -776,6 +776,26 @@ function interpretPreflight(result) {
776
776
  // error is not a safe preflight — block rather than proceed to a switch.
777
777
  const data = parseSuccessEnvelopeData(result);
778
778
  if (data === null) {
779
+ // CLI bootstrap failures use an ok:false envelope on stderr. Preserve its
780
+ // already-sanitized diagnosis (including recovery guidance), never promote
781
+ // an error envelope to a successful storage preflight.
782
+ for (const stream of [result.stderr, result.stdout]) {
783
+ let failure;
784
+ try {
785
+ failure = JSON.parse(stream.toString("utf8").trim());
786
+ }
787
+ catch {
788
+ continue;
789
+ }
790
+ if (!isRecord(failure) || failure.ok !== false || typeof failure.message !== "string")
791
+ continue;
792
+ return {
793
+ status: "blocked",
794
+ stage: "preflight",
795
+ message: `Staged binary preflight failed (exit ${result.status ?? "null"}): ${failure.message}`,
796
+ action: "Resolve the reported staged-binary failure using the same Home and matching CLI; do not force the update."
797
+ };
798
+ }
779
799
  return {
780
800
  status: "blocked",
781
801
  stage: "preflight",
@@ -24,6 +24,7 @@ import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagn
24
24
  import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
25
25
  import { requireManagedGlobalCaller, requireManagedGlobalReader, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
26
26
  import { sessionCallerEnvironment, requireManagedCaller } from "./runtime/sessionCaller.js";
27
+ import { maintenanceCallerEnvironment } from "./storage/maintenanceCaller.js";
27
28
  import { checkCurrentSkills, SKILL_REUSE_RULE } from "./context/currentSkills.js";
28
29
  import { roleSkillIds } from "./context/roleSessionContext.js";
29
30
  import { hasManagedIdentity } from "./runtime/managedIdentity.js";
@@ -1999,6 +2000,23 @@ async function preflightManagedTaskControlPlane() {
1999
2000
  if (taskFinalReviewInvocation.error !== undefined) {
2000
2001
  throw new Error(taskFinalReviewInvocation.error);
2001
2002
  }
2003
+ if (["doctor", "upgrade", "update"].includes(args[0] ?? "")) {
2004
+ const home = resolveYuiHome(process.env);
2005
+ Object.assign(process.env, maintenanceCallerEnvironment(home, process.env, args[0] !== "doctor"));
2006
+ if (!hasManagedIdentity(process.env)
2007
+ || (process.env.YUI_SESSION_SCOPE === "global" && process.env.YUI_ROLE === "operator")) {
2008
+ if (process.env.YUI_SESSION_SCOPE === "global") {
2009
+ const manifest = assertManagedSessionManifest(home, "global");
2010
+ if (manifest !== undefined && manifest.roleKind !== "operator") {
2011
+ throw new ManagedRuntimeDriftError("Managed global invocation does not match its Session Manifest.");
2012
+ }
2013
+ }
2014
+ if (taskFinalReviewInvocation.request !== undefined) {
2015
+ throw new Error("Task final-review contract cannot be established by Home maintenance.");
2016
+ }
2017
+ return { contract: undefined, verifiedStore: undefined };
2018
+ }
2019
+ }
2002
2020
  // Desktop clients may carry only their provider-native identity. Resolve
2003
2021
  // command-local hints before any public/Task/Global routing. This reads this
2004
2022
  // Home only, and never creates a Session or mutates its registration.
@@ -2094,11 +2112,6 @@ async function preflightManagedGlobalControlPlane() {
2094
2112
  || manifest.roleKind !== expectedRoleKind)) {
2095
2113
  throw new ManagedRuntimeDriftError("Managed global invocation does not match its Session Manifest.");
2096
2114
  }
2097
- if (expectedRoleKind === "operator" && ["doctor", "upgrade", "update"].includes(args[0] ?? "")) {
2098
- // These commands inspect/adopt the storage contract themselves. Requiring
2099
- // current storage before reaching upgrade would make recovery impossible.
2100
- return { contract: undefined, verifiedStore: undefined };
2101
- }
2102
2115
  await assertRuntimeCoherence({ actualHome: home }, {
2103
2116
  checkController: !(expectedRoleKind === "operator" && operatorOfflineCommand(args))
2104
2117
  });
@@ -0,0 +1,69 @@
1
+ import { join } from "node:path";
2
+ import Database from "../sqlite/driver.js";
3
+ import { hasManagedIdentity } from "../runtime/managedIdentity.js";
4
+ import { ManagedRuntimeDriftError, requireManagedGlobalCaller } from "../runtime/managedCaller.js";
5
+ import { sessionCallerEnvironment } from "../runtime/sessionCaller.js";
6
+ import { validateStoredRecord } from "./recordValidation.js";
7
+ import { inspectSqliteSchema, storageMinorUpgradePlan, validateSqliteSchemaObjects } from "./sqliteSchema.js";
8
+ import { CURRENT_DATABASE_FILENAME } from "./storageSchema.js";
9
+ /**
10
+ * Maintenance must authenticate before migration, without opening a current-only
11
+ * business Store. The declared 1.x migrations preserve these Role/Session
12
+ * envelopes and physical tables. Read only those records in one SQLite snapshot;
13
+ * this grants no old-format business reads, writes or implicit migration.
14
+ */
15
+ export function maintenanceCallerEnvironment(home, env, operatorOnly = true) {
16
+ if (!hasManagedIdentity(env))
17
+ return env;
18
+ const db = new Database(join(home, CURRENT_DATABASE_FILENAME), { readonly: true, fileMustExist: true });
19
+ try {
20
+ return db.transaction(() => {
21
+ const schema = inspectSqliteSchema(db);
22
+ if (storageMinorUpgradePlan(schema.currentVersion) === null) {
23
+ throw new ManagedRuntimeDriftError("Maintenance cannot authenticate an unsupported Home storage version.");
24
+ }
25
+ validateSqliteSchemaObjects(db);
26
+ const read = (table, where, params) => {
27
+ const row = db.prepare(`SELECT payload FROM ${table} WHERE ${where}`).get(...params);
28
+ if (!row)
29
+ return null;
30
+ const value = JSON.parse(row.payload);
31
+ validateStoredRecord(table, value);
32
+ return value;
33
+ };
34
+ const registry = {
35
+ getGlobalRole: (name) => read("global_roles", "name=?", [name]),
36
+ getGlobalRoleSessionSet: (name) => read("global_role_session_sets", "name=?", [name]),
37
+ getTaskRoleSessionSet: (task, role) => read("role_session_sets", "task_id=? AND role_name=?", [task, role]),
38
+ listRuntimeSessionCandidates: () => {
39
+ // Read canonical sets, not a derived index that could be stale. Only
40
+ // active bindings participate, exactly as in normal caller discovery.
41
+ const rows = db.prepare(`
42
+ SELECT 'global_role_session_sets' AS source, payload FROM global_role_session_sets
43
+ UNION ALL SELECT 'role_session_sets' AS source, payload FROM role_session_sets
44
+ `).all();
45
+ return rows.flatMap(row => {
46
+ const set = JSON.parse(row.payload);
47
+ validateStoredRecord(row.source, set);
48
+ const session = set.sessions[set.activeAgentId];
49
+ if (!session || session.status !== "active")
50
+ return [];
51
+ return [{ owner: set.owner, agentId: session.agentId, adapterId: session.adapterId,
52
+ nativeSessionId: session.nativeSessionId, sessionUpdatedAt: set.updatedAt }];
53
+ });
54
+ }
55
+ };
56
+ const current = sessionCallerEnvironment(registry, env);
57
+ if (current.YUI_SESSION_SCOPE !== "global" || current.YUI_ROLE !== "operator") {
58
+ if (!operatorOnly)
59
+ return current; // Doctor keeps normal scoped authorization below.
60
+ throw new ManagedRuntimeDriftError("Home maintenance requires the user or current Operator, not a Task or custom Role.");
61
+ }
62
+ requireManagedGlobalCaller(registry, current);
63
+ return current;
64
+ })();
65
+ }
66
+ finally {
67
+ db.close();
68
+ }
69
+ }
@@ -119,7 +119,7 @@ function objects(db) {
119
119
  WHERE name NOT GLOB 'sqlite_*' ORDER BY type,name`).all();
120
120
  }
121
121
  let expectedObjects;
122
- function validateSchemaObjects(db) {
122
+ export function validateSqliteSchemaObjects(db) {
123
123
  if (expectedObjects === undefined) {
124
124
  const reference = new Database(":memory:");
125
125
  try {
@@ -161,7 +161,7 @@ export function inspectSqliteSchema(db) {
161
161
  throw new SqliteSchemaError("baseline checksum does not match its version.");
162
162
  }
163
163
  if (version === CURRENT_STORAGE_VERSION)
164
- validateSchemaObjects(db);
164
+ validateSqliteSchemaObjects(db);
165
165
  return {
166
166
  currentVersion: version, currentChecksum: row.checksum,
167
167
  targetVersion: CURRENT_STORAGE_VERSION, targetChecksum: CURRENT_SCHEMA_CHECKSUM,
@@ -51,6 +51,28 @@ merely to avoid a version decision. Package versions remain distinct from protoc
51
51
  and persistent-storage contracts: their compatibility checks and required migrations
52
52
  are unchanged, and the storage rules below do not authorize a package release.
53
53
 
54
+ ### 2.8.1 compatibility and migration notes
55
+
56
+ This patch fixes maintenance startup for an existing Operator conversation
57
+ that supplies only its native identity, including Codex Desktop. `doctor`,
58
+ `upgrade` and `update` authenticate against the supported Home before requiring
59
+ current storage, so an older Home can reach the updater's read-only preflight.
60
+ Structured preflight failures retain their original diagnosis and recovery
61
+ guidance. Ordinary commands still require current storage and current authority.
62
+
63
+ Storage remains **1.8**. The existing contiguous **1.0 through 1.8** migration
64
+ chain, backup, maintenance lock and exact Controller handover are unchanged.
65
+ Use `yui update --version 2.8.1`; no manual intermediate upgrade or replacement
66
+ native Session is required for supported Homes. Pre-1.0 / 0.x Home formats are
67
+ outside this release's supported migration contract; no historical authorization
68
+ exception or legacy conversion is introduced.
69
+
70
+ Verification includes an isolated published 2.7.1 CLI with storage 1.7 and a
71
+ live Controller/Host: one native-only update preserves the original Host and
72
+ native conversation, migrates storage and processes new input afterward.
73
+ Only the external Provider is simulated. Publishing does not update a user's
74
+ global CLI, Home or production Controller.
75
+
54
76
  ### 2.8.0 compatibility and migration notes
55
77
 
56
78
  This minor adds native metabridge model management: provider configuration,
@@ -36,6 +36,22 @@ Agent 选择一个预先声明的计划,设施从持久状态驱动该计划
36
36
  不能将不兼容冒称为兼容,也不为回避版本选择新增历史兼容机制。包版本与协议、
37
37
  持久存储契约仍然独立:兼容性检查和必要迁移要求不变,下文存储规则不授予发版权限。
38
38
 
39
+ ### 2.8.1 兼容性及迁移说明
40
+
41
+ 本 patch 修复已有 Operator 会话仅携带 native 身份(包括 Codex Desktop)时的
42
+ 维护启动循环。`doctor`、`upgrade`、`update` 先在受支持的旧 Home 上验证调用者,
43
+ 不再要求存储已经升级才能进入只读预检。结构化预检失败保留原始诊断与恢复指引;
44
+ 普通业务命令仍要求当前存储格式和有效权限。
45
+
46
+ 存储仍为 **1.8**,已有 **1.0 至 1.8** 连续迁移链、备份、维护锁和精确 Controller
47
+ 交接保持不变。可使用 `yui update --version 2.8.1`;受支持的 Home 不需要手工先做
48
+ 中间升级或更换 native 会话。1.0 之前/0.x Home 格式不在本次迁移支持范围内,
49
+ 本版本不引入旧格式转换或历史授权例外。
50
+
51
+ 隔离验证使用真实已发布的 2.7.1 CLI、1.7 Home 和运行中的 Controller/Host:
52
+ 一次 native-only update 保留原 Host 与 native 会话,完成存储迁移并继续处理新输入。
53
+ 仅外部 Provider 使用确定性模拟。发布不会更新用户真实全局 CLI、Home 或生产 Controller。
54
+
39
55
  ### 2.8.0 兼容性及迁移说明
40
56
 
41
57
  本 minor 新增原生 metabridge 模型管理:提供商配置、模型目录缓存,以及经受支持
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zq-silk/yui",
3
- "version": "2.8.0",
3
+ "version": "2.8.1",
4
4
  "description": "Local control plane for long-running native agent CLI sessions backed by tmux.",
5
5
  "license": "MIT",
6
6
  "private": false,