Fix macOS notarization timestamps

This commit is contained in:
coso
2026-08-05 19:43:37 +08:00
parent 36b692aac0
commit 1021efbbc3
8 changed files with 169 additions and 11 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ Simplified Chinese release notes are the primary version.
- Added Rust, Vitest, contract, and real Electron Gate B coverage for Plugin v2 App Center, typed gateway, mention/activation, MCP resources, Right Surface, history restoration, and uninstall semantics.
- Added regression guards for Electron packaged assets, embedded Browser HTML, GUI smoke, Skills watcher behavior, and the tool lifecycle.
- Added canonical reasoning linear-persistence and 1,200-command history-import performance regressions covering repeated deltas, final snapshot replacement, and background progress completion.
- Added a macOS release-signing policy that disables timestamps for nested resources and retries when Apple's timestamp service is unavailable.
- Added a macOS release-signing policy that disables timestamps only for non-code resources, retries when Apple's timestamp service is unavailable, and preserves secure timestamps for nested Mach-O binaries required by notarization.
### Documentation
+1 -1
View File
@@ -24,7 +24,7 @@
- 新增 Plugin v2 App Center、typed gateway、mention/activation、MCP resource、Right Surface、历史恢复与卸载语义的 Rust、Vitest、contract 和真实 Electron Gate B 覆盖。
- 补充 Electron 打包资产、内嵌 Browser HTML、GUI smoke、Skills watcher 和 tool lifecycle 的回归守卫。
- 新增 canonical reasoning 线性持久化与 1,200 条命令历史导入性能回归,覆盖重复 delta、final 快照替换和后台进度完成。
- 增加 macOS 正式签名嵌套资源的 timestamp 关闭策略与时间戳服务不可用重试守卫。
- 增加 macOS 正式签名非代码资源的 timestamp 关闭策略与时间戳服务不可用重试守卫,同时确保嵌套 Mach-O 保留安全时间戳并可通过 notarization
### 文档
+17 -1
View File
@@ -61,6 +61,19 @@ function isTopLevelAppBundle(filePath) {
return normalized.endsWith(".app") && !normalized.includes(".app/");
}
const MACOS_NON_CODE_RESOURCE_EXTENSIONS = new Set([
".asar",
".bin",
".dat",
".pak",
]);
function isMacOSNonCodeResource(filePath) {
const normalized = String(filePath || "").replace(/\\/g, "/");
const extension = path.extname(normalized).toLowerCase();
return MACOS_NON_CODE_RESOURCE_EXTENSIONS.has(extension);
}
function macSignOptions({
env = process.env,
platform = process.platform,
@@ -79,8 +92,11 @@ function macSignOptions({
preEmbedProvisioningProfile: false,
optionsForFile: (filePath) => {
if (!isTopLevelAppBundle(filePath)) {
if (releaseSigning && isMacOSNonCodeResource(filePath)) {
return { timestamp: "none" };
}
return releaseSigning
? { timestamp: "none" }
? {}
: { hardenedRuntime: false, timestamp: "none" };
}
return {
+1 -1
View File
@@ -51,7 +51,7 @@
- `cargo fmt --manifest-path "lime-rs/Cargo.toml" --all -- --check`:通过。
- `git diff --check`:通过。
- GUI 构建只输出已知非阻断告警:`oem-runtime-config.js` 非 module script、Browserslist 数据过期与 Electron `console-message` API 废弃提示;本次未更新核心依赖。
- 发布后 CI `30997182812` 首轮失败于 macOS `codesign --timestamp` 处理 `locale.pak`,错误为 `The timestamp service is not available.`;已修复嵌套资源 timestamp 策略,并将该错误纳入 Forge package 重试分类。
- 发布后 CI `30997182812` 首轮失败于 macOS `codesign --timestamp` 处理 `locale.pak`,错误为 `The timestamp service is not available.`;已修复非代码资源 timestamp 策略,并将该错误纳入 Forge package 重试分类。重跑 `31000622796` 暴露此前对全部嵌套文件关闭 timestamp 会导致 notarization 拒绝嵌套 Mach-O;现已仅对 `.pak``.bin``.dat``.asar` 资源关闭 timestamp,嵌套代码恢复安全 timestamp。
- 发布后补充 Agent chat terminal session-detail refresh 迁移及 contract guardForge 配置、发布工作流、相关守卫与 Agent chat 定向测试均通过。
## 架构确认
+5
View File
@@ -61,6 +61,11 @@ describe("Electron Forge config", () => {
macSignOptions({ env, platform: "darwin" }).optionsForFile(
"release-electron/Lime-darwin-arm64/Lime.app/Contents/MacOS/Lime",
),
).toEqual({});
expect(
macSignOptions({ env, platform: "darwin" }).optionsForFile(
"release-electron/Lime-darwin-arm64/Lime.app/Contents/Resources/fa_FEMININE.lproj/locale.pak",
),
).toEqual({ timestamp: "none" });
expect(macNotarizeOptions({ env, platform: "darwin" })).toEqual({
appleId: "release@example.com",
@@ -633,6 +633,8 @@ function assertForgeConfig(forgeConfigPath = DEFAULT_FORGE_CONFIG_PATH) {
"optionsForFile",
"MACOS_APP_ENTITLEMENTS",
"isTopLevelAppBundle",
"MACOS_NON_CODE_RESOURCE_EXTENSIONS",
"isMacOSNonCodeResource",
'!normalized.includes(".app/")',
"const releaseSigning =",
'env.LIME_ELECTRON_SIGN === "1"',
@@ -640,6 +642,7 @@ function assertForgeConfig(forgeConfigPath = DEFAULT_FORGE_CONFIG_PATH) {
"identityValidation: releaseSigning",
"return releaseSigning",
': { hardenedRuntime: false, timestamp: "none" }',
'return { timestamp: "none" }',
"if (!releaseSigning)",
'options.identity = "-"',
"lime-rs/entitlements.plist",
@@ -487,6 +487,137 @@ describe("agentSessionState runtimeSync detail refresh", () => {
expect(result.snapshot.threadRead?.status).toBe("completed");
});
it("历史回合的已停止标记不能污染后续已完成回合", () => {
const canceledTurnId = "turn-runtime-sync-canceled-history";
const completedTurnId = "turn-runtime-sync-continued-completed";
const continuedText = "继续输出已恢复:正文已经正常收口";
const detail = {
id: "topic-runtime-sync-cancel-then-continue",
created_at: 1782800000,
updated_at: 1782800004,
messages: [
{
role: "user",
timestamp: 1782800000,
content: [{ type: "text", text: "整理今天的国际新闻" }],
},
{
role: "assistant",
timestamp: 1782800001,
content: [{ type: "text", text: "(已停止)" }],
},
{
role: "user",
timestamp: 1782800002,
content: [{ type: "text", text: "继续输出" }],
},
{
role: "assistant",
timestamp: 1782800003,
content: [{ type: "text", text: continuedText }],
},
],
turns: [
{
id: canceledTurnId,
thread_id: "topic-runtime-sync-cancel-then-continue-thread",
prompt_text: "整理今天的国际新闻",
status: "canceled",
started_at: "2026-07-10T00:00:00.000Z",
completed_at: "2026-07-10T00:00:01.000Z",
created_at: "2026-07-10T00:00:00.000Z",
updated_at: "2026-07-10T00:00:01.000Z",
},
{
id: completedTurnId,
thread_id: "topic-runtime-sync-cancel-then-continue-thread",
prompt_text: "继续输出",
status: "completed",
started_at: "2026-07-10T00:00:02.000Z",
completed_at: "2026-07-10T00:00:04.000Z",
created_at: "2026-07-10T00:00:02.000Z",
updated_at: "2026-07-10T00:00:04.000Z",
},
],
items: [
createAgentMessageItem({
id: "item-runtime-sync-canceled-history",
thread_id: "topic-runtime-sync-cancel-then-continue-thread",
turn_id: canceledTurnId,
text: "(已停止)",
sequence: 1,
}),
createAgentMessageItem({
id: "item-runtime-sync-continued-completed",
thread_id: "topic-runtime-sync-cancel-then-continue-thread",
turn_id: completedTurnId,
text: continuedText,
sequence: 2,
}),
],
thread_read: {
thread_id: "topic-runtime-sync-cancel-then-continue-thread",
status: "completed",
active_turn_id: completedTurnId,
turns: [
{ turn_id: canceledTurnId, status: "canceled" },
{ turn_id: completedTurnId, status: "completed" },
],
},
} satisfies AgentSessionDetail;
const result = buildHydratedAgentSessionSnapshot({
topicId: "topic-runtime-sync-cancel-then-continue",
detail,
currentSessionId: "topic-runtime-sync-cancel-then-continue",
currentMessages: [
createMessage({
id: "local-canceled-user",
role: "user",
content: "整理今天的国际新闻",
runtimeTurnId: canceledTurnId,
}),
createMessage({
id: "local-canceled-assistant",
role: "assistant",
content: "(已停止)",
contentParts: [{ type: "text", text: "(已停止)" }],
isThinking: false,
runtimeTurnId: canceledTurnId,
}),
createMessage({
id: "local-continued-user",
role: "user",
content: "继续输出",
runtimeTurnId: completedTurnId,
}),
createMessage({
id: "local-continued-assistant",
role: "assistant",
content: continuedText,
isThinking: false,
runtimeTurnId: completedTurnId,
}),
],
currentThreadTurns: [],
currentThreadItems: [],
currentExecutionRuntime: null,
currentExecutionStrategy: "react",
topics: [],
detailMergeMode: "terminal_reconcile",
});
const continuedAssistant = result.snapshot.messages.find(
(message) => message.runtimeTurnId === completedTurnId,
);
expect(continuedAssistant).toMatchObject({
role: "assistant",
content: continuedText,
isThinking: false,
});
expect(continuedAssistant?.content).not.toContain("(已停止)");
});
it("terminalReconcile detached canceled detail 应迁移 pending-turn 已停止终态", () => {
const realTurnId = "turn-runtime-sync-detached-canceled-real";
const promptText = "整理今天的国际新闻";
@@ -271,13 +271,16 @@ function collectLocalInterruptedMarkerTurnIds(options: {
}
}
pushTurnId(options.fallbackTurnId);
for (
let index = (options.incomingItems?.length ?? 0) - 1;
index >= 0;
index -= 1
) {
pushTurnId(readTimelineItemTurnId(options.incomingItems?.[index]));
// 没有具体运行回合归属时,才从当前/入站时间线推断停止回合。
if (interruptedTurnIds.size === 0) {
pushTurnId(options.fallbackTurnId);
for (
let index = (options.incomingItems?.length ?? 0) - 1;
index >= 0;
index -= 1
) {
pushTurnId(readTimelineItemTurnId(options.incomingItems?.[index]));
}
}
return interruptedTurnIds;